Pārlūkot izejas kodu

newuser controlledservlet refactoring

Jason Rivard 8 gadi atpakaļ
vecāks
revīzija
a275893cef

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

@@ -140,7 +140,7 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
 
     protected abstract void nextStep(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException;
 
-    public abstract void preProcessCheck(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException;
+    public abstract ProcessStatus preProcessCheck(PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException;
 
     void sendOtherRedirect(final PwmRequest pwmRequest, final String location) throws IOException, PwmUnrecoverableException {
         final String protocol = pwmRequest.getHttpServletRequest().getProtocol();

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

@@ -244,7 +244,8 @@ public class LogoutServlet extends ControlledPwmServlet {
     }
 
     @Override
-    public void preProcessCheck(final PwmRequest pwmRequest) {
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) {
         // no checks required, this is a public module.
+        return ProcessStatus.Continue;
     }
 }

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

@@ -126,14 +126,14 @@ public class SetupResponsesServlet extends ControlledPwmServlet {
     }
 
     @Override
-    public void preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean(pwmRequest);
 
         if (!pwmSession.isAuthenticated()) {
             pwmRequest.respondWithError(PwmError.ERROR_AUTHENTICATION_REQUIRED.toInfo());
-            return;
+            return ProcessStatus.Halt;
         }
 
         if (pwmSession.getLoginInfoBean().getType() == AuthenticationType.AUTH_WITHOUT_PASSWORD) {
@@ -165,6 +165,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet {
             LOGGER.debug(pwmSession, errorInformation);
             throw new PwmUnrecoverableException(errorInformation);
         }
+
+        return ProcessStatus.Continue;
     }
 
     @ActionHandler(action = "confirmResponses")

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

@@ -443,17 +443,19 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
     }
 
     @Override
-    public void preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         if (!pwmRequest.getPwmApplication().getConfig().readSettingAsBoolean(PwmSetting.UPDATE_PROFILE_ENABLE)) {
             pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "Setting " + PwmSetting.UPDATE_PROFILE_ENABLE.toMenuLocationDebug(null,null) + " is not enabled."));
-            return;
+            return ProcessStatus.Halt;
         }
 
         final UpdateAttributesProfile updateAttributesProfile = getProfile(pwmRequest);
         if (updateAttributesProfile == null) {
             pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_NO_PROFILE_ASSIGNED));
-            return;
+            return ProcessStatus.Halt;
         }
+
+        return ProcessStatus.Continue;
     }
 
 

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

@@ -598,7 +598,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
     }
 
     @Override
-    public void preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final ChangePasswordBean changePasswordBean = pwmApplication.getSessionStateService().getBean(pwmRequest, ChangePasswordBean.class);
@@ -609,7 +609,8 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
 
         if (!pwmRequest.isAuthenticated()) {
             pwmRequest.respondWithError(PwmError.ERROR_AUTHENTICATION_REQUIRED.toInfo());
-            return;
+            return ProcessStatus.Halt;
+
         }
 
         if (determineIfCurrentPasswordRequired(pwmApplication, pwmSession)) {
@@ -618,7 +619,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
 
         if (!pwmSession.getSessionManager().checkPermission(pwmApplication, Permission.CHANGE_PASSWORD)) {
             pwmRequest.respondWithError(PwmError.ERROR_UNAUTHORIZED.toInfo());
-            return;
+            return ProcessStatus.Halt;
         }
 
         try {
@@ -628,5 +629,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet {
         } catch (ChaiException e) {
             throw PwmUnrecoverableException.fromChaiException(e);
         }
+
+        return ProcessStatus.Continue;
     }
 }

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

@@ -190,7 +190,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
     }
 
     @Override
-    public void preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
 
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -200,12 +200,12 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
 
         if (!config.readSettingAsBoolean(PwmSetting.FORGOTTEN_PASSWORD_ENABLE)) {
             pwmRequest.respondWithError(PwmError.ERROR_SERVICE_NOT_AVAILABLE.toInfo());
-            return;
+            return ProcessStatus.Halt;
         }
 
         if (pwmSession.isAuthenticated()) {
             pwmRequest.respondWithError(PwmError.ERROR_USERAUTHENTICATED.toInfo());
-            return;
+            return ProcessStatus.Halt;
         }
 
         if (forgottenPasswordBean.getUserIdentity() != null) {
@@ -213,6 +213,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
         }
 
         checkForLocaleSwitch(pwmRequest, forgottenPasswordBean);
+
+        return ProcessStatus.Continue;
     }
 
     /*

+ 2 - 2
src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java

@@ -80,7 +80,7 @@ class NewUserFormUtils {
         return new NewUserBean.NewUserForm(FormUtility.asStringMap(userFormValues), passwordData1, passwordData2);
     }
 
-    static NewUserServlet.NewUserTokenData fromTokenPayload(
+    static NewUserTokenData fromTokenPayload(
             final PwmRequest pwmRequest,
             final TokenPayload tokenPayload
     )
@@ -130,7 +130,7 @@ class NewUserFormUtils {
             passwordData = null;
         }
         final NewUserBean.NewUserForm newUserForm = new NewUserBean.NewUserForm(FormUtility.asStringMap(userFormValues), passwordData, passwordData);
-        return new NewUserServlet.NewUserTokenData(newUserProfile.getIdentifier(), newUserForm);
+        return new NewUserTokenData(newUserProfile.getIdentifier(), newUserForm);
     }
 
     static Map<String, String> toTokenPayload(

+ 99 - 598
src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -22,30 +22,16 @@
 
 package password.pwm.http.servlet.newuser;
 
-import com.novell.ldapchai.ChaiFactory;
-import com.novell.ldapchai.ChaiUser;
-import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import com.novell.ldapchai.provider.ChaiConfiguration;
-import com.novell.ldapchai.provider.ChaiProvider;
-import com.novell.ldapchai.provider.ChaiProviderFactory;
-import com.novell.ldapchai.provider.ChaiSetting;
-import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.bean.EmailItemBean;
-import password.pwm.bean.LoginInfoBean;
-import password.pwm.bean.SessionLabel;
 import password.pwm.bean.TokenVerificationProgress;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserInfoBean;
-import password.pwm.config.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.option.TokenStorageMethod;
-import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
@@ -54,34 +40,23 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.JspUrl;
+import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.NewUserBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
-import password.pwm.util.CaptchaUtility;
+import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.UserDataReader;
-import password.pwm.ldap.UserSearchEngine;
-import password.pwm.ldap.auth.PwmAuthenticationSource;
-import password.pwm.ldap.auth.SessionAuthenticator;
-import password.pwm.svc.event.AuditEvent;
-import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.token.TokenPayload;
-import password.pwm.svc.token.TokenService;
-import password.pwm.util.java.JavaHelper;
+import password.pwm.util.CaptchaUtility;
 import password.pwm.util.java.JsonUtil;
-import password.pwm.util.PasswordData;
 import password.pwm.util.java.Percent;
-import password.pwm.util.RandomPasswordGenerator;
-import password.pwm.util.java.StringUtil;
 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.PasswordUtility;
-import password.pwm.ws.client.rest.RestTokenDataClient;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestCheckPasswordServer;
 
@@ -89,13 +64,11 @@ import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -115,7 +88,7 @@ import java.util.Set;
                 PwmConstants.URL_PREFIX_PUBLIC + "/NewUser/*",
         }
 )
-public class NewUserServlet extends AbstractPwmServlet {
+public class NewUserServlet extends ControlledPwmServlet {
     private static final PwmLogger LOGGER = PwmLogger.forClass(NewUserServlet.class);
 
     static final String FIELD_PASSWORD1 = "password1";
@@ -158,99 +131,51 @@ public class NewUserServlet extends AbstractPwmServlet {
     }
 
 
-    protected void processAction(final PwmRequest pwmRequest)
-            throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
-    {
+    private static NewUserBean getNewUserBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+        return pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, NewUserBean.class);
+    }
+
+
+
+    @Override
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
 
-        final NewUserAction action = this.readProcessAction(pwmRequest);
         final Configuration config = pwmApplication.getConfig();
 
         if (!config.readSettingAsBoolean(PwmSetting.NEWUSER_ENABLE)) {
-            pwmRequest.respondWithError(PwmError.ERROR_SERVICE_NOT_AVAILABLE.toInfo());
-            return;
+            throw new PwmUnrecoverableException(PwmError.ERROR_SERVICE_NOT_AVAILABLE);
         }
 
         final NewUserBean newUserBean = pwmApplication.getSessionStateService().getBean(pwmRequest, NewUserBean.class);
 
         // convert a url command like /public/newuser/profile/xxx to set profile.
         if (readProfileFromUrl(pwmRequest, newUserBean)) {
-            return;
+            return ProcessStatus.Halt;
         }
 
+        final NewUserAction action = this.readProcessAction(pwmRequest);
+
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
         if (action == null) {
             if (pwmRequest.convertURLtokenCommand()) {
-                return;
+                return ProcessStatus.Halt;
             }
-        }
-
-        if (action != null) {
-            // authenticated actions
-            switch (action) {
-                case checkProgress:
-                    restCheckProgress(pwmRequest, newUserBean);
-                    return;
-
-                case complete:
-                    handleComplete(pwmRequest, newUserBean);
-                    return;
-
-                default:
-                    // continue to next section
-                    break;
-            }
-
-            if (pwmSession.isAuthenticated()) {
+        } else if (action != NewUserAction.complete && action != NewUserAction.checkProgress ) {
+            if (pwmRequest.isAuthenticated()) {
                 pwmRequest.respondWithError(PwmError.ERROR_USERAUTHENTICATED.toInfo());
-                return;
-            }
-
-            switch (action) {
-                case profileChoice:
-                    handleProfileChoiceRequest(pwmRequest, newUserBean);
-                    return;
-
-                case processForm:
-                    handleProcessFormRequest(pwmRequest, newUserBean);
-                    return;
-
-                case validate:
-                    restValidateForm(pwmRequest);
-                    return;
-
-                case enterCode:
-                    handleEnterCodeRequest(pwmRequest, newUserBean);
-                    break;
-
-                case reset:
-                    pwmApplication.getSessionStateService().clearBean(pwmRequest, NewUserBean.class);
-                    pwmRequest.sendRedirectToContinue();
-                    break;
-
-                case agree:
-                    LOGGER.debug(pwmSession, "user accepted new-user agreement");
-                    newUserBean.setAgreementPassed(true);
-                    break;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(action);
-
+                return ProcessStatus.Halt;
             }
         }
 
-        if (!pwmRequest.getPwmResponse().isCommitted()) {
-            this.advancedToNextStage(pwmRequest, newUserBean);
-        }
+        return ProcessStatus.Continue;
     }
 
-    private void advancedToNextStage(
-            final PwmRequest pwmRequest,
-            final NewUserBean newUserBean
-    )
-            throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
+    @Override
+    protected void nextStep(final PwmRequest pwmRequest)
+         throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
+        final NewUserBean newUserBean = getNewUserBean(pwmRequest);
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
 
@@ -285,7 +210,7 @@ public class NewUserServlet extends AbstractPwmServlet {
         final TokenVerificationProgress tokenVerificationProgress = newUserBean.getTokenVerificationProgress();
         if (newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_EMAIL_VERIFICATION)) {
             if (!tokenVerificationProgress.getIssuedTokens().contains(TokenVerificationProgress.TokenChannel.EMAIL)) {
-                initializeToken(pwmRequest, TokenVerificationProgress.TokenChannel.EMAIL);
+                NewUserUtils.initializeToken(pwmRequest, newUserBean, TokenVerificationProgress.TokenChannel.EMAIL);
             }
 
             if (!tokenVerificationProgress.getPassedTokens().contains(TokenVerificationProgress.TokenChannel.EMAIL)) {
@@ -296,7 +221,7 @@ public class NewUserServlet extends AbstractPwmServlet {
 
         if (newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_SMS_VERIFICATION)) {
             if (!newUserBean.getTokenVerificationProgress().getIssuedTokens().contains(TokenVerificationProgress.TokenChannel.SMS)) {
-                initializeToken(pwmRequest, TokenVerificationProgress.TokenChannel.SMS);
+                NewUserUtils.initializeToken(pwmRequest, newUserBean, TokenVerificationProgress.TokenChannel.SMS);
             }
 
             if (!newUserBean.getTokenVerificationProgress().getPassedTokens().contains(TokenVerificationProgress.TokenChannel.SMS)) {
@@ -309,7 +234,7 @@ public class NewUserServlet extends AbstractPwmServlet {
                 pwmSession.getSessionStateBean().getLocale());
         if (newUserAgreementText != null && !newUserAgreementText.isEmpty()) {
             if (!newUserBean.isAgreementPassed()) {
-                final MacroMachine macroMachine = createMacroMachineForNewUser(
+                final MacroMachine macroMachine = NewUserUtils.createMacroMachineForNewUser(
                         pwmApplication,
                         pwmRequest.getSessionLabel(),
                         newUserBean.getNewUserForm()
@@ -326,23 +251,25 @@ public class NewUserServlet extends AbstractPwmServlet {
         }
 
         // success so create the new user.
-        final String newUserDN = determineUserDN(pwmRequest, newUserBean.getNewUserForm());
+        final String newUserDN = NewUserUtils.determineUserDN(pwmRequest, newUserBean.getNewUserForm());
 
         try {
-            createUser(newUserBean.getNewUserForm(), pwmRequest, newUserDN);
+            NewUserUtils.createUser(newUserBean.getNewUserForm(), pwmRequest, newUserDN);
             newUserBean.setCreateStartTime(new Date());
             pwmRequest.forwardToJsp(JspUrl.NEW_USER_WAIT);
         } catch (PwmOperationalException e) {
             LOGGER.error(pwmRequest, "error during user creation: " + e.getMessage());
             if (newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_DELETE_ON_FAIL)) {
-                deleteUserAccount(newUserDN, pwmRequest);
+                NewUserUtils.deleteUserAccount(newUserDN, pwmRequest);
             }
             LOGGER.error(pwmSession, e.getErrorInformation().toDebugStr());
             pwmRequest.respondWithError(e.getErrorInformation());
         }
     }
 
-    protected boolean readProfileFromUrl(final PwmRequest pwmRequest, final NewUserBean newUserBean) throws ChaiUnavailableException, PwmUnrecoverableException, ServletException, IOException {
+    private boolean readProfileFromUrl(final PwmRequest pwmRequest, final NewUserBean newUserBean)
+            throws PwmUnrecoverableException, ServletException, IOException
+    {
         final String PROFILE_URL_SEGMENT = "profile";
         final String urlRemainder = servletUriRemainder(pwmRequest, PROFILE_URL_SEGMENT);
 
@@ -367,8 +294,8 @@ public class NewUserServlet extends AbstractPwmServlet {
         return false;
     }
 
-
-    protected static void restValidateForm(
+    @ActionHandler(action = "validate")
+    private ProcessStatus restValidateForm(
             final PwmRequest pwmRequest
     )
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
@@ -399,6 +326,8 @@ public class NewUserServlet extends AbstractPwmServlet {
             LOGGER.debug(pwmRequest, "error while validating new user form: " + e.getMessage());
             pwmRequest.outputJsonResult(restResultBean);
         }
+
+        return ProcessStatus.Halt;
     }
 
     static PasswordUtility.PasswordCheckInfo verifyForm(
@@ -436,24 +365,8 @@ public class NewUserServlet extends AbstractPwmServlet {
         );
     }
 
-    static void passwordCheckInfoToException(final PasswordUtility.PasswordCheckInfo passwordCheckInfo)
-            throws PwmOperationalException
-    {
-        if (!passwordCheckInfo.isPassed()) {
-            final ErrorInformation errorInformation = PwmError.forErrorNumber(passwordCheckInfo.getErrorCode()).toInfo();
-            throw new PwmOperationalException(errorInformation);
-        }
-        if (passwordCheckInfo.getMatch() != PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH) {
-            final ErrorInformation errorInformation = PwmError.PASSWORD_DOESNOTMATCH.toInfo();
-            throw new PwmOperationalException(errorInformation);
-        }
-
-    }
-
-    private void handleEnterCodeRequest(
-            final PwmRequest pwmRequest,
-            final NewUserBean newUserBean
-    )
+    @ActionHandler(action = "enterCode")
+    private ProcessStatus handleEnterCodeRequest(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ServletException, ChaiUnavailableException
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -470,9 +383,10 @@ public class NewUserServlet extends AbstractPwmServlet {
                     userEnteredCode
             );
             if (tokenPayload != null) {
+                final NewUserBean newUserBean = getNewUserBean(pwmRequest);
                 final NewUserTokenData newUserTokenData = NewUserFormUtils.fromTokenPayload(pwmRequest, tokenPayload);
-                newUserBean.setProfileID(newUserTokenData.profileID);
-                final NewUserBean.NewUserForm newUserFormFromToken = newUserTokenData.formData;
+                newUserBean.setProfileID(newUserTokenData.getProfileID());
+                final NewUserBean.NewUserForm newUserFormFromToken = newUserTokenData.getFormData();
                 if (password.pwm.svc.token.TokenType.NEWUSER_EMAIL.matchesName(tokenPayload.getName())) {
                     LOGGER.debug(pwmRequest, "email token passed");
 
@@ -517,19 +431,21 @@ public class NewUserServlet extends AbstractPwmServlet {
                 errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT);
             }
             LOGGER.debug(pwmSession, errorInformation.toDebugStr());
-            pwmRequest.setResponseError(errorInformation);
+            setLastError(pwmRequest, errorInformation);
         }
 
-        this.advancedToNextStage(pwmRequest, newUserBean);
+        return ProcessStatus.Continue;
     }
 
-
-    private void handleProfileChoiceRequest(final PwmRequest pwmRequest, final NewUserBean newUserBean)
+    @ActionHandler(action = "profileChoice")
+    private ProcessStatus handleProfileChoiceRequest(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
     {
         final Set<String> profileIDs = pwmRequest.getConfig().getNewUserProfiles().keySet();
         final String requestedProfileID = pwmRequest.readParameterAsString("profile");
 
+        final NewUserBean newUserBean = getNewUserBean(pwmRequest);
+
         if (requestedProfileID == null || requestedProfileID.isEmpty()) {
             newUserBean.setProfileID(null);
         }
@@ -537,18 +453,21 @@ public class NewUserServlet extends AbstractPwmServlet {
             newUserBean.setProfileID(requestedProfileID);
         }
 
-        this.advancedToNextStage(pwmRequest, newUserBean);
+        return ProcessStatus.Continue;
     }
 
-    private void handleProcessFormRequest(final PwmRequest pwmRequest, final NewUserBean newUserBean)
+    @ActionHandler(action = "processForm")
+    private ProcessStatus handleProcessFormRequest(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
     {
+        final NewUserBean newUserBean = getNewUserBean(pwmRequest);
+
         if (!CaptchaUtility.verifyReCaptcha(pwmRequest)) {
             final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE);
             LOGGER.debug(pwmRequest, errorInfo);
-            pwmRequest.setResponseError(errorInfo);
+            setLastError(pwmRequest, errorInfo);
             forwardToFormPage(pwmRequest, newUserBean);
-            return;
+            return ProcessStatus.Halt;
         }
 
         newUserBean.setFormPassed(false);
@@ -557,442 +476,29 @@ public class NewUserServlet extends AbstractPwmServlet {
         try {
             final NewUserBean.NewUserForm newUserForm = NewUserFormUtils.readFromRequest(pwmRequest);
             final PasswordUtility.PasswordCheckInfo passwordCheckInfo = verifyForm(pwmRequest, newUserForm, true);
-            passwordCheckInfoToException(passwordCheckInfo);
+            NewUserUtils.passwordCheckInfoToException(passwordCheckInfo);
             newUserBean.setNewUserForm(newUserForm);
             newUserBean.setFormPassed(true);
-            this.advancedToNextStage(pwmRequest, newUserBean);
         } catch (PwmOperationalException e) {
-            pwmRequest.setResponseError(e.getErrorInformation());
+            setLastError(pwmRequest, e.getErrorInformation());
             forwardToFormPage(pwmRequest, newUserBean);
+            return ProcessStatus.Halt;
         }
+        return ProcessStatus.Continue;
     }
 
 
-    private static void createUser(
-            final NewUserBean.NewUserForm newUserForm,
-            final PwmRequest pwmRequest,
-            final String newUserDN
-    )
-            throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException
-    {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-
-        final long startTime = System.currentTimeMillis();
-
-        // re-perform verification before proceeding
-        {
-            final PasswordUtility.PasswordCheckInfo passwordCheckInfo = verifyForm(
-                    pwmRequest,
-                    newUserForm,
-                    false
-            );
-            passwordCheckInfoToException(passwordCheckInfo);
-        }
-
-        LOGGER.debug(pwmSession, "beginning createUser process for " + newUserDN);
-        final PasswordData userPassword = newUserForm.getNewUserPassword();
-
-        // set up the user creation attributes
-        final Map<String, String> createAttributes = NewUserFormUtils.getLdapDataFromNewUserForm(getNewUserProfile(pwmRequest), newUserForm);
-
-        // read the creation object classes from configuration
-        final Set<String> createObjectClasses = new LinkedHashSet<>(
-                pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES));
-
-        // add the auto-add object classes
-        {
-            final LdapProfile defaultLDAPProfile = pwmApplication.getConfig().getDefaultLdapProfile();
-            createObjectClasses.addAll(defaultLDAPProfile.readSettingAsStringArray(PwmSetting.AUTO_ADD_OBJECT_CLASSES));
-        }
-
-        final ChaiProvider chaiProvider = pwmApplication.getConfig().getDefaultLdapProfile().getProxyChaiProvider(pwmApplication);
-        try { // create the ldap entry
-            chaiProvider.createEntry(newUserDN, createObjectClasses, createAttributes);
-
-            LOGGER.info(pwmSession, "created user entry: " + newUserDN);
-        } catch (ChaiOperationException e) {
-            final String userMessage = "unexpected ldap error creating user entry: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                    userMessage);
-            throw new PwmOperationalException(errorInformation);
-        }
-
-        final ChaiUser theUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
-        final NewUserProfile newUserProfile = getNewUserProfile(pwmRequest);
-
-        final boolean useTempPw;
-        {
-            final String settingValue = pwmApplication.getConfig().readAppProperty(AppProperty.NEWUSER_LDAP_USE_TEMP_PW);
-            if ("auto".equalsIgnoreCase(settingValue)) {
-                useTempPw = chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY;
-            } else {
-                useTempPw = Boolean.parseBoolean(settingValue);
-            }
-        }
-
-        if (useTempPw) {
-            LOGGER.trace(pwmSession, "will use temporary password process for new user entry: " + newUserDN);
-            final PasswordData temporaryPassword;
-            {
-                final RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = new RandomPasswordGenerator.RandomGeneratorConfig();
-                randomGeneratorConfig.setPasswordPolicy(newUserProfile.getNewUserPasswordPolicy(pwmApplication, pwmRequest.getLocale()));
-                temporaryPassword = RandomPasswordGenerator.createRandomPassword(pwmSession.getLabel(), randomGeneratorConfig, pwmApplication);
-            }
-            final ChaiUser proxiedUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
-            try { //set password as admin
-                proxiedUser.setPassword(temporaryPassword.getStringValue());
-                LOGGER.debug(pwmSession, "set temporary password for new user entry: " + newUserDN);
-            } catch (ChaiOperationException e) {
-                final String userMessage = "unexpected ldap error setting temporary password for new user entry: " + e.getMessage();
-                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                        userMessage);
-                throw new PwmOperationalException(errorInformation);
-            }
-
-            // add AD-specific attributes
-            if (ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
-                try {
-                    LOGGER.debug(pwmSession,
-                            "setting userAccountControl attribute to enable account " + theUser.getEntryDN());
-                    theUser.writeStringAttribute("userAccountControl", "512");
-                } catch (ChaiOperationException e) {
-                    final String errorMsg = "error enabling AD account when writing userAccountControl attribute: " + e.getMessage();
-                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                            errorMsg);
-                    throw new PwmOperationalException(errorInformation);
-                }
-            }
-
-            try { // bind as user
-                LOGGER.debug(pwmSession,
-                        "attempting bind as user to then allow changing to requested password for new user entry: " + newUserDN);
-                final ChaiConfiguration chaiConfiguration = new ChaiConfiguration(chaiProvider.getChaiConfiguration());
-                chaiConfiguration.setSetting(ChaiSetting.BIND_DN, newUserDN);
-                chaiConfiguration.setSetting(ChaiSetting.BIND_PASSWORD, temporaryPassword.getStringValue());
-                final ChaiProvider bindAsProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
-                final ChaiUser bindAsUser = ChaiFactory.createChaiUser(newUserDN, bindAsProvider);
-                bindAsUser.changePassword(temporaryPassword.getStringValue(), userPassword.getStringValue());
-                LOGGER.debug(pwmSession, "changed to user requested password for new user entry: " + newUserDN);
-                bindAsProvider.close();
-            } catch (ChaiOperationException e) {
-                final String userMessage = "unexpected ldap error setting user password for new user entry: " + e.getMessage();
-                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                        userMessage);
-                throw new PwmOperationalException(errorInformation);
-            }
-        } else {
-            try { //set password
-                theUser.setPassword(userPassword.getStringValue());
-                LOGGER.debug(pwmSession, "set user requested password for new user entry: " + newUserDN);
-            } catch (ChaiOperationException e) {
-                final String userMessage = "unexpected ldap error setting password for new user entry: " + e.getMessage();
-                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                        userMessage);
-                throw new PwmOperationalException(errorInformation);
-            }
-
-            // add AD-specific attributes
-            if (ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
-                try {
-                    theUser.writeStringAttribute("userAccountControl", "512");
-                } catch (ChaiOperationException e) {
-                    final String errorMsg = "error enabling AD account when writing userAccountControl attribute: " + e.getMessage();
-                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                            errorMsg);
-                    throw new PwmOperationalException(errorInformation);
-                }
-            }
-        }
-
-        LOGGER.trace(pwmSession, "new user ldap creation process complete, now authenticating user");
-
-        //authenticate the user to pwm
-        final UserIdentity userIdentity = new UserIdentity(newUserDN, pwmApplication.getConfig().getDefaultLdapProfile().getIdentifier());
-        final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(pwmApplication, pwmSession, PwmAuthenticationSource.NEW_USER_REGISTRATION);
-        sessionAuthenticator.authenticateUser(userIdentity, userPassword);
-
-        {  // execute configured actions
-            final List<ActionConfiguration> actions = newUserProfile.readSettingAsAction(
-                    PwmSetting.NEWUSER_WRITE_ATTRIBUTES);
-            if (actions != null && !actions.isEmpty()) {
-                LOGGER.debug(pwmSession, "executing configured actions to user " + theUser.getEntryDN());
-
-                final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity)
-                        .setExpandPwmMacros(true)
-                        .setMacroMachine(pwmSession.getSessionManager().getMacroMachine(pwmApplication))
-                        .createActionExecutor();
-
-                actionExecutor.executeActions(actions, pwmSession);
-            }
-        }
-
-        // send user email
-        sendNewUserEmailConfirmation(pwmRequest);
-
-
-        // add audit record
-        pwmApplication.getAuditManager().submit(AuditEvent.CREATE_USER, pwmSession.getUserInfoBean(), pwmSession);
-
-        // increment the new user creation statistics
-        pwmApplication.getStatisticsManager().incrementValue(Statistic.NEW_USERS);
-
-        LOGGER.debug(pwmSession, "completed createUser process for " + newUserDN + " (" + TimeDuration.fromCurrent(
-                startTime).asCompactString() + ")");
-    }
-
-    private static void deleteUserAccount(
-            final String userDN,
+    @ActionHandler(action = "checkProgress")
+    private ProcessStatus restCheckProgress(
             final PwmRequest pwmRequest
     )
-            throws PwmUnrecoverableException
+            throws IOException, ServletException, PwmUnrecoverableException
     {
-        try {
-            LOGGER.warn(pwmRequest, "deleting ldap user account " + userDN);
-            pwmRequest.getConfig().getDefaultLdapProfile().getProxyChaiProvider(pwmRequest.getPwmApplication()).deleteEntry(userDN);
-            LOGGER.warn(pwmRequest, "ldap user account " + userDN + " has been deleted");
-        } catch (ChaiUnavailableException | ChaiOperationException e) {
-            LOGGER.error(pwmRequest, "error deleting ldap user account " + userDN + ", " + e.getMessage());
-        }
-
-        pwmRequest.getPwmSession().unauthenticateUser(pwmRequest);
-    }
-
-    private static String determineUserDN(
-            final PwmRequest pwmRequest,
-            final NewUserBean.NewUserForm formValues
-    )
-            throws PwmUnrecoverableException, ChaiUnavailableException
-    {
-        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), formValues);
-        final NewUserProfile newUserProfile = getNewUserProfile(pwmRequest);
-        final List<String> configuredNames = newUserProfile.readSettingAsStringArray(PwmSetting.NEWUSER_USERNAME_DEFINITION);
-        final List<String> failedValues = new ArrayList<>();
-
-        final String configuredContext = newUserProfile.readSettingAsString(PwmSetting.NEWUSER_CONTEXT);
-        final String expandedContext = macroMachine.expandMacros(configuredContext);
-
-
-        if (configuredNames == null || configuredNames.isEmpty() || configuredNames.iterator().next().isEmpty()) {
-            final String namingAttribute = pwmRequest.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE);
-            String namingValue = null;
-            for (final String formKey : formValues.getFormData().keySet()) {
-                if (formKey.equals(namingAttribute)) {
-                    namingValue = formValues.getFormData().get(formKey);
-                }
-            }
-            if (namingValue == null || namingValue.isEmpty()) {
-                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                        "username definition not set, and naming attribute is not present in form"));
-            }
-            final String escapedName = StringUtil.escapeLdapDN(namingValue);
-            final String generatedDN = namingAttribute + "=" + escapedName + "," + expandedContext;
-            LOGGER.debug(pwmRequest, "generated dn for new user: " + generatedDN);
-            return generatedDN;
-        }
-
-        int attemptCount = 0;
-        final String generatedDN;
-        while (attemptCount < configuredNames.size()) {
-            final String expandedName;
-            {
-                {
-                    final String configuredName = configuredNames.get(attemptCount);
-                    expandedName = macroMachine.expandMacros(configuredName);
-                }
-
-                if (!testIfEntryNameExists(pwmRequest, expandedName)) {
-                    LOGGER.trace(pwmRequest, "generated entry name for new user is unique: " + expandedName);
-                    final String namingAttribute = pwmRequest.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE);
-                    final String escapedName = StringUtil.escapeLdapDN(expandedName);
-                    generatedDN = namingAttribute + "=" + escapedName + "," + expandedContext;
-                    LOGGER.debug(pwmRequest, "generated dn for new user: " + generatedDN);
-                    return generatedDN;
-                } else {
-                    failedValues.add(expandedName);
-                }
-            }
-
-            LOGGER.debug(pwmRequest, "generated entry name for new user is not unique, will try again");
-            attemptCount++;
-        }
-        LOGGER.error(pwmRequest,
-                "failed to generate new user DN after " + attemptCount + " attempts, failed values: " + JsonUtil.serializeCollection(
-                        failedValues));
-        throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
-                "unable to generate a unique DN value"));
-    }
-
-    private static boolean testIfEntryNameExists(
-            final PwmRequest pwmRequest,
-            final String rdnValue
-    )
-            throws PwmUnrecoverableException, ChaiUnavailableException
-    {
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setUsername(rdnValue);
-        try {
-            final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
-                    searchConfiguration, 2, Collections.<String>emptyList());
-            return results != null && !results.isEmpty();
-        } catch (PwmOperationalException e) {
-            final String msg = "ldap error while searching for duplicate entry names: " + e.getMessage();
-            LOGGER.error(pwmRequest, msg);
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE, msg));
-        }
-    }
-
-    private static void sendNewUserEmailConfirmation(
-            final PwmRequest pwmRequest
-    )
-            throws PwmUnrecoverableException, ChaiUnavailableException
-    {
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final UserInfoBean userInfoBean = pwmSession.getUserInfoBean();
-        final Configuration config = pwmRequest.getConfig();
-        final Locale locale = pwmSession.getSessionStateBean().getLocale();
-        final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_NEWUSER, locale);
-
-        if (configuredEmailSetting == null) {
-            LOGGER.debug(pwmSession,
-                    "skipping send of new user email for '" + userInfoBean.getUserIdentity().getUserDN() + "' no email configured");
-            return;
-        }
-
-        pwmRequest.getPwmApplication().getEmailQueue().submitEmail(
-                configuredEmailSetting,
-                pwmSession.getUserInfoBean(),
-                pwmSession.getSessionManager().getMacroMachine(pwmRequest.getPwmApplication())
-        );
-    }
-
-    public void initializeToken(
-            final PwmRequest pwmRequest,
-            final TokenVerificationProgress.TokenChannel tokenType
-    )
-            throws PwmUnrecoverableException
-    {
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-
-        if (pwmApplication.getConfig().getTokenStorageMethod() == TokenStorageMethod.STORE_LDAP) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{
-                    "cannot generate new user tokens when storage type is configured as STORE_LDAP.",
-            }));
-        }
-
-        final NewUserBean newUserBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, NewUserBean.class);
-        final Configuration config = pwmApplication.getConfig();
-        final Map<String, String> tokenPayloadMap = NewUserFormUtils.toTokenPayload(pwmRequest, newUserBean.getNewUserForm());
-        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmApplication, pwmRequest.getSessionLabel(), newUserBean.getNewUserForm());
-
-        switch (tokenType) {
-            case SMS: {
-                final String toNum = tokenPayloadMap.get(pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.SMS_USER_PHONE_ATTRIBUTE));
-
-                final RestTokenDataClient.TokenDestinationData inputTokenDestData = new RestTokenDataClient.TokenDestinationData(
-                        null, toNum, null);
-                final RestTokenDataClient restTokenDataClient = new RestTokenDataClient(pwmApplication);
-                final RestTokenDataClient.TokenDestinationData outputDestTokenData = restTokenDataClient.figureDestTokenDisplayString(
-                        pwmRequest.getSessionLabel(),
-                        inputTokenDestData,
-                        null,
-                        pwmRequest.getLocale());
-
-                final String tokenKey;
-                try {
-                    final TokenPayload tokenPayload = pwmApplication.getTokenService().createTokenPayload(
-                            password.pwm.svc.token.TokenType.NEWUSER_SMS,
-                            tokenPayloadMap,
-                            null,
-                            Collections.singleton(outputDestTokenData.getSms())
-                    );
-                    tokenKey = pwmApplication.getTokenService().generateNewToken(tokenPayload,
-                            pwmRequest.getSessionLabel());
-                } catch (PwmOperationalException e) {
-                    throw new PwmUnrecoverableException(e.getErrorInformation());
-                }
-
-                final String message = config.readSettingAsLocalizedString(PwmSetting.SMS_NEWUSER_TOKEN_TEXT,
-                        pwmSession.getSessionStateBean().getLocale());
-
-                try {
-                    TokenService.TokenSender.sendSmsToken(pwmApplication, null, macroMachine,
-                            outputDestTokenData.getSms(), message, tokenKey);
-                } catch (Exception e) {
-                    throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
-                }
-
-                newUserBean.getTokenVerificationProgress().getIssuedTokens().add(TokenVerificationProgress.TokenChannel.SMS);
-                newUserBean.getTokenVerificationProgress().setTokenDisplayText(outputDestTokenData.getDisplayValue());
-                newUserBean.getTokenVerificationProgress().setPhase(TokenVerificationProgress.TokenChannel.SMS);
-            }
-            break;
-
-            case EMAIL: {
-                final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(
-                        PwmSetting.EMAIL_NEWUSER_VERIFICATION, pwmSession.getSessionStateBean().getLocale());
-                final String toAddress = macroMachine.expandMacros(configuredEmailSetting.getTo());
-
-                final RestTokenDataClient.TokenDestinationData inputTokenDestData = new RestTokenDataClient.TokenDestinationData(
-                        toAddress, null, null);
-                final RestTokenDataClient restTokenDataClient = new RestTokenDataClient(pwmApplication);
-                final RestTokenDataClient.TokenDestinationData outputDestTokenData = restTokenDataClient.figureDestTokenDisplayString(
-                        pwmRequest.getSessionLabel(),
-                        inputTokenDestData,
-                        null,
-                        pwmRequest.getLocale());
-
-                final String tokenKey;
-                try {
-                    final TokenPayload tokenPayload = pwmApplication.getTokenService().createTokenPayload(
-                            password.pwm.svc.token.TokenType.NEWUSER_EMAIL,
-                            tokenPayloadMap,
-                            null,
-                            Collections.singleton(outputDestTokenData.getEmail())
-                    );
-                    tokenKey = pwmApplication.getTokenService().generateNewToken(tokenPayload,
-                            pwmRequest.getSessionLabel());
-                } catch (PwmOperationalException e) {
-                    throw new PwmUnrecoverableException(e.getErrorInformation());
-                }
-
-                newUserBean.getTokenVerificationProgress().getIssuedTokens().add(TokenVerificationProgress.TokenChannel.EMAIL);
-                newUserBean.getTokenVerificationProgress().setPhase(TokenVerificationProgress.TokenChannel.EMAIL);
-                newUserBean.getTokenVerificationProgress().setTokenDisplayText(outputDestTokenData.getDisplayValue());
-
-                final EmailItemBean emailItemBean = new EmailItemBean(
-                        outputDestTokenData.getEmail(),
-                        configuredEmailSetting.getFrom(),
-                        configuredEmailSetting.getSubject(),
-                        configuredEmailSetting.getBodyPlain().replace("%TOKEN%", tokenKey),
-                        configuredEmailSetting.getBodyHtml().replace("%TOKEN%", tokenKey));
-
-                try {
-                    TokenService.TokenSender.sendEmailToken(pwmApplication, null, macroMachine, emailItemBean,
-                            outputDestTokenData.getEmail(), tokenKey);
-                } catch (Exception e) {
-                    throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
-                }
-            }
-            break;
-
-            default:
-                LOGGER.error("Unimplemented token purpose: " + tokenType);
-                newUserBean.getTokenVerificationProgress().setPhase(null);
-        }
-    }
-
-    private void restCheckProgress(
-            final PwmRequest pwmRequest,
-            final NewUserBean newUserBean
-    )
-            throws IOException, ServletException, PwmUnrecoverableException {
+        final NewUserBean newUserBean = getNewUserBean(pwmRequest);
         final Date startTime = newUserBean.getCreateStartTime();
         if (startTime == null) {
             pwmRequest.respondWithError(PwmError.ERROR_INCORRECT_REQ_SEQUENCE.toInfo(), true);
-            return;
+            return ProcessStatus.Halt;
         }
 
         final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
@@ -1021,18 +527,46 @@ public class NewUserServlet extends AbstractPwmServlet {
 
         LOGGER.trace(pwmRequest, "returning result for restCheckProgress: " + JsonUtil.serialize(restResultBean));
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    private void handleComplete(
-            final PwmRequest pwmRequest,
-            final NewUserBean newUserBean
+    @ActionHandler(action = "agree")
+    private ProcessStatus handleAgree(
+            final PwmRequest pwmRequest
     )
             throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
+        LOGGER.debug(pwmRequest, "user accepted new-user agreement");
+
+        final NewUserBean newUserBean = getNewUserBean(pwmRequest);
+        newUserBean.setAgreementPassed(true);
+
+        return ProcessStatus.Continue;
+    }
+
+    @ActionHandler(action = "reset")
+    private ProcessStatus handleReset(
+            final PwmRequest pwmRequest
+    )
+            throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
+    {
+        pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, NewUserBean.class);
+        pwmRequest.sendRedirectToContinue();
+
+        return ProcessStatus.Continue;
+    }
+
+    @ActionHandler(action = "complete")
+    private ProcessStatus handleComplete(
+            final PwmRequest pwmRequest
+    )
+            throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
+    {
+        final NewUserBean newUserBean = getNewUserBean(pwmRequest);
         final Date startTime = newUserBean.getCreateStartTime();
         if (startTime == null) {
             pwmRequest.respondWithError(PwmError.ERROR_INCORRECT_REQ_SEQUENCE.toInfo(), true);
-            return;
+            return ProcessStatus.Halt;
         }
 
         final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
@@ -1042,35 +576,12 @@ public class NewUserServlet extends AbstractPwmServlet {
         // be sure minimum wait time has passed
         if (new Date().before(completeTime)) {
             pwmRequest.forwardToJsp(JspUrl.NEW_USER_WAIT);
-            return;
+            return ProcessStatus.Halt;
         }
 
         pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, NewUserBean.class);
         pwmRequest.getPwmResponse().forwardToSuccessPage(Message.Success_CreateUser);
-    }
-
-    private static MacroMachine createMacroMachineForNewUser(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final NewUserBean.NewUserForm newUserForm
-    )
-            throws PwmUnrecoverableException
-    {
-        final Map<String, String> formValues = newUserForm.getFormData();
-        final UserInfoBean stubUserBean = new UserInfoBean();
-
-        final String emailAddressAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(
-                PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE);
-        stubUserBean.setUserEmailAddress(formValues.get(emailAddressAttribute));
-
-        final String usernameAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_USERNAME_ATTRIBUTE);
-        stubUserBean.setUsername(formValues.get(usernameAttribute));
-
-        final LoginInfoBean stubLoginBean = new LoginInfoBean();
-        stubLoginBean.setUserCurrentPassword(newUserForm.getNewUserPassword());
-
-        final UserDataReader stubReader = new NewUserUserDataReader(formValues);
-        return new MacroMachine(pwmApplication, sessionLabel, stubUserBean, stubLoginBean, stubReader);
+        return ProcessStatus.Halt;
     }
 
 
@@ -1079,16 +590,6 @@ public class NewUserServlet extends AbstractPwmServlet {
         return profile.readSettingAsForm(PwmSetting.NEWUSER_FORM);
     }
 
-    static class NewUserTokenData {
-        private String profileID;
-        private NewUserBean.NewUserForm formData;
-
-        NewUserTokenData(final String profileID, final NewUserBean.NewUserForm formData) {
-            this.profileID = profileID;
-            this.formData = formData;
-        }
-    }
-
     public static NewUserProfile getNewUserProfile(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
         final String profileID = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, NewUserBean.class).getProfileID();
         if (profileID == null) {

+ 45 - 0
src/main/java/password/pwm/http/servlet/newuser/NewUserTokenData.java

@@ -0,0 +1,45 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.servlet.newuser;
+
+import password.pwm.http.bean.NewUserBean;
+
+import java.io.Serializable;
+
+class NewUserTokenData implements Serializable {
+    private String profileID;
+    private NewUserBean.NewUserForm formData;
+
+    NewUserTokenData(final String profileID, final NewUserBean.NewUserForm formData) {
+        this.profileID = profileID;
+        this.formData = formData;
+    }
+
+    public String getProfileID() {
+        return profileID;
+    }
+
+    public NewUserBean.NewUserForm getFormData() {
+        return formData;
+    }
+}

+ 542 - 0
src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -0,0 +1,542 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.servlet.newuser;
+
+import com.novell.ldapchai.ChaiFactory;
+import com.novell.ldapchai.ChaiUser;
+import com.novell.ldapchai.exception.ChaiOperationException;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import com.novell.ldapchai.provider.ChaiConfiguration;
+import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.provider.ChaiProviderFactory;
+import com.novell.ldapchai.provider.ChaiSetting;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.bean.EmailItemBean;
+import password.pwm.bean.LoginInfoBean;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.TokenVerificationProgress;
+import password.pwm.bean.UserIdentity;
+import password.pwm.bean.UserInfoBean;
+import password.pwm.config.ActionConfiguration;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.option.TokenStorageMethod;
+import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.profile.NewUserProfile;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmSession;
+import password.pwm.http.bean.NewUserBean;
+import password.pwm.ldap.UserDataReader;
+import password.pwm.ldap.UserSearchEngine;
+import password.pwm.ldap.auth.PwmAuthenticationSource;
+import password.pwm.ldap.auth.SessionAuthenticator;
+import password.pwm.svc.event.AuditEvent;
+import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.token.TokenPayload;
+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.StringUtil;
+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.PasswordUtility;
+import password.pwm.ws.client.rest.RestTokenDataClient;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+public class NewUserUtils {
+    private static PwmLogger LOGGER = password.pwm.util.logging.PwmLogger.forClass(NewUserUtils.class);
+    
+    private NewUserUtils() {
+    }
+
+
+    static void passwordCheckInfoToException(final PasswordUtility.PasswordCheckInfo passwordCheckInfo)
+            throws PwmOperationalException
+    {
+        if (!passwordCheckInfo.isPassed()) {
+            final ErrorInformation errorInformation = PwmError.forErrorNumber(passwordCheckInfo.getErrorCode()).toInfo();
+            throw new PwmOperationalException(errorInformation);
+        }
+        if (passwordCheckInfo.getMatch() != PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH) {
+            final ErrorInformation errorInformation = PwmError.PASSWORD_DOESNOTMATCH.toInfo();
+            throw new PwmOperationalException(errorInformation);
+        }
+
+    }
+
+    static void createUser(
+            final NewUserBean.NewUserForm newUserForm,
+            final PwmRequest pwmRequest,
+            final String newUserDN
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+
+        final long startTime = System.currentTimeMillis();
+
+        // re-perform verification before proceeding
+        {
+            final PasswordUtility.PasswordCheckInfo passwordCheckInfo = NewUserServlet.verifyForm(
+                    pwmRequest,
+                    newUserForm,
+                    false
+            );
+            passwordCheckInfoToException(passwordCheckInfo);
+        }
+
+        NewUserUtils.LOGGER.debug(pwmSession, "beginning createUser process for " + newUserDN);
+        final PasswordData userPassword = newUserForm.getNewUserPassword();
+
+        // set up the user creation attributes
+        final Map<String, String> createAttributes = NewUserFormUtils.getLdapDataFromNewUserForm(NewUserServlet.getNewUserProfile(pwmRequest), newUserForm);
+
+        // read the creation object classes from configuration
+        final Set<String> createObjectClasses = new LinkedHashSet<>(
+                pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES));
+
+        // add the auto-add object classes
+        {
+            final LdapProfile defaultLDAPProfile = pwmApplication.getConfig().getDefaultLdapProfile();
+            createObjectClasses.addAll(defaultLDAPProfile.readSettingAsStringArray(PwmSetting.AUTO_ADD_OBJECT_CLASSES));
+        }
+
+        final ChaiProvider chaiProvider = pwmApplication.getConfig().getDefaultLdapProfile().getProxyChaiProvider(pwmApplication);
+        try { // create the ldap entry
+            chaiProvider.createEntry(newUserDN, createObjectClasses, createAttributes);
+
+            NewUserUtils.LOGGER.info(pwmSession, "created user entry: " + newUserDN);
+        } catch (ChaiOperationException e) {
+            final String userMessage = "unexpected ldap error creating user entry: " + e.getMessage();
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                    userMessage);
+            throw new PwmOperationalException(errorInformation);
+        }
+
+        final ChaiUser theUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
+        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
+
+        final boolean useTempPw;
+        {
+            final String settingValue = pwmApplication.getConfig().readAppProperty(AppProperty.NEWUSER_LDAP_USE_TEMP_PW);
+            if ("auto".equalsIgnoreCase(settingValue)) {
+                useTempPw = chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY;
+            } else {
+                useTempPw = Boolean.parseBoolean(settingValue);
+            }
+        }
+
+        if (useTempPw) {
+            NewUserUtils.LOGGER.trace(pwmSession, "will use temporary password process for new user entry: " + newUserDN);
+            final PasswordData temporaryPassword;
+            {
+                final RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = new RandomPasswordGenerator.RandomGeneratorConfig();
+                randomGeneratorConfig.setPasswordPolicy(newUserProfile.getNewUserPasswordPolicy(pwmApplication, pwmRequest.getLocale()));
+                temporaryPassword = RandomPasswordGenerator.createRandomPassword(pwmSession.getLabel(), randomGeneratorConfig, pwmApplication);
+            }
+            final ChaiUser proxiedUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
+            try { //set password as admin
+                proxiedUser.setPassword(temporaryPassword.getStringValue());
+                NewUserUtils.LOGGER.debug(pwmSession, "set temporary password for new user entry: " + newUserDN);
+            } catch (ChaiOperationException e) {
+                final String userMessage = "unexpected ldap error setting temporary password for new user entry: " + e.getMessage();
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                        userMessage);
+                throw new PwmOperationalException(errorInformation);
+            }
+
+            // add AD-specific attributes
+            if (ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
+                try {
+                    NewUserUtils.LOGGER.debug(pwmSession,
+                            "setting userAccountControl attribute to enable account " + theUser.getEntryDN());
+                    theUser.writeStringAttribute("userAccountControl", "512");
+                } catch (ChaiOperationException e) {
+                    final String errorMsg = "error enabling AD account when writing userAccountControl attribute: " + e.getMessage();
+                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                            errorMsg);
+                    throw new PwmOperationalException(errorInformation);
+                }
+            }
+
+            try { // bind as user
+                NewUserUtils.LOGGER.debug(pwmSession,
+                        "attempting bind as user to then allow changing to requested password for new user entry: " + newUserDN);
+                final ChaiConfiguration chaiConfiguration = new ChaiConfiguration(chaiProvider.getChaiConfiguration());
+                chaiConfiguration.setSetting(ChaiSetting.BIND_DN, newUserDN);
+                chaiConfiguration.setSetting(ChaiSetting.BIND_PASSWORD, temporaryPassword.getStringValue());
+                final ChaiProvider bindAsProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
+                final ChaiUser bindAsUser = ChaiFactory.createChaiUser(newUserDN, bindAsProvider);
+                bindAsUser.changePassword(temporaryPassword.getStringValue(), userPassword.getStringValue());
+                NewUserUtils.LOGGER.debug(pwmSession, "changed to user requested password for new user entry: " + newUserDN);
+                bindAsProvider.close();
+            } catch (ChaiOperationException e) {
+                final String userMessage = "unexpected ldap error setting user password for new user entry: " + e.getMessage();
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                        userMessage);
+                throw new PwmOperationalException(errorInformation);
+            }
+        } else {
+            try { //set password
+                theUser.setPassword(userPassword.getStringValue());
+                NewUserUtils.LOGGER.debug(pwmSession, "set user requested password for new user entry: " + newUserDN);
+            } catch (ChaiOperationException e) {
+                final String userMessage = "unexpected ldap error setting password for new user entry: " + e.getMessage();
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                        userMessage);
+                throw new PwmOperationalException(errorInformation);
+            }
+
+            // add AD-specific attributes
+            if (ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY == chaiProvider.getDirectoryVendor()) {
+                try {
+                    theUser.writeStringAttribute("userAccountControl", "512");
+                } catch (ChaiOperationException e) {
+                    final String errorMsg = "error enabling AD account when writing userAccountControl attribute: " + e.getMessage();
+                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                            errorMsg);
+                    throw new PwmOperationalException(errorInformation);
+                }
+            }
+        }
+
+        NewUserUtils.LOGGER.trace(pwmSession, "new user ldap creation process complete, now authenticating user");
+
+        //authenticate the user to pwm
+        final UserIdentity userIdentity = new UserIdentity(newUserDN, pwmApplication.getConfig().getDefaultLdapProfile().getIdentifier());
+        final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(pwmApplication, pwmSession, PwmAuthenticationSource.NEW_USER_REGISTRATION);
+        sessionAuthenticator.authenticateUser(userIdentity, userPassword);
+
+        {  // execute configured actions
+            final List<ActionConfiguration> actions = newUserProfile.readSettingAsAction(
+                    PwmSetting.NEWUSER_WRITE_ATTRIBUTES);
+            if (actions != null && !actions.isEmpty()) {
+                NewUserUtils.LOGGER.debug(pwmSession, "executing configured actions to user " + theUser.getEntryDN());
+
+                final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity)
+                        .setExpandPwmMacros(true)
+                        .setMacroMachine(pwmSession.getSessionManager().getMacroMachine(pwmApplication))
+                        .createActionExecutor();
+
+                actionExecutor.executeActions(actions, pwmSession);
+            }
+        }
+
+        // send user email
+        sendNewUserEmailConfirmation(pwmRequest);
+
+
+        // add audit record
+        pwmApplication.getAuditManager().submit(AuditEvent.CREATE_USER, pwmSession.getUserInfoBean(), pwmSession);
+
+        // increment the new user creation statistics
+        pwmApplication.getStatisticsManager().incrementValue(Statistic.NEW_USERS);
+
+        NewUserUtils.LOGGER.debug(pwmSession, "completed createUser process for " + newUserDN + " (" + TimeDuration.fromCurrent(
+                startTime).asCompactString() + ")");
+    }
+
+    static void deleteUserAccount(
+            final String userDN,
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException
+    {
+        try {
+            NewUserUtils.LOGGER.warn(pwmRequest, "deleting ldap user account " + userDN);
+            pwmRequest.getConfig().getDefaultLdapProfile().getProxyChaiProvider(pwmRequest.getPwmApplication()).deleteEntry(userDN);
+            NewUserUtils.LOGGER.warn(pwmRequest, "ldap user account " + userDN + " has been deleted");
+        } catch (ChaiUnavailableException | ChaiOperationException e) {
+            NewUserUtils.LOGGER.error(pwmRequest, "error deleting ldap user account " + userDN + ", " + e.getMessage());
+        }
+
+        pwmRequest.getPwmSession().unauthenticateUser(pwmRequest);
+    }
+
+    static String determineUserDN(
+            final PwmRequest pwmRequest,
+            final NewUserBean.NewUserForm formValues
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException
+    {
+        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), formValues);
+        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
+        final List<String> configuredNames = newUserProfile.readSettingAsStringArray(PwmSetting.NEWUSER_USERNAME_DEFINITION);
+        final List<String> failedValues = new ArrayList<>();
+
+        final String configuredContext = newUserProfile.readSettingAsString(PwmSetting.NEWUSER_CONTEXT);
+        final String expandedContext = macroMachine.expandMacros(configuredContext);
+
+
+        if (configuredNames == null || configuredNames.isEmpty() || configuredNames.iterator().next().isEmpty()) {
+            final String namingAttribute = pwmRequest.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE);
+            String namingValue = null;
+            for (final String formKey : formValues.getFormData().keySet()) {
+                if (formKey.equals(namingAttribute)) {
+                    namingValue = formValues.getFormData().get(formKey);
+                }
+            }
+            if (namingValue == null || namingValue.isEmpty()) {
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                        "username definition not set, and naming attribute is not present in form"));
+            }
+            final String escapedName = StringUtil.escapeLdapDN(namingValue);
+            final String generatedDN = namingAttribute + "=" + escapedName + "," + expandedContext;
+            NewUserUtils.LOGGER.debug(pwmRequest, "generated dn for new user: " + generatedDN);
+            return generatedDN;
+        }
+
+        int attemptCount = 0;
+        final String generatedDN;
+        while (attemptCount < configuredNames.size()) {
+            final String expandedName;
+            {
+                {
+                    final String configuredName = configuredNames.get(attemptCount);
+                    expandedName = macroMachine.expandMacros(configuredName);
+                }
+
+                if (!testIfEntryNameExists(pwmRequest, expandedName)) {
+                    NewUserUtils.LOGGER.trace(pwmRequest, "generated entry name for new user is unique: " + expandedName);
+                    final String namingAttribute = pwmRequest.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_NAMING_ATTRIBUTE);
+                    final String escapedName = StringUtil.escapeLdapDN(expandedName);
+                    generatedDN = namingAttribute + "=" + escapedName + "," + expandedContext;
+                    NewUserUtils.LOGGER.debug(pwmRequest, "generated dn for new user: " + generatedDN);
+                    return generatedDN;
+                } else {
+                    failedValues.add(expandedName);
+                }
+            }
+
+            NewUserUtils.LOGGER.debug(pwmRequest, "generated entry name for new user is not unique, will try again");
+            attemptCount++;
+        }
+        NewUserUtils.LOGGER.error(pwmRequest,
+                "failed to generate new user DN after " + attemptCount + " attempts, failed values: " + JsonUtil.serializeCollection(
+                        failedValues));
+        throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE,
+                "unable to generate a unique DN value"));
+    }
+
+    private static boolean testIfEntryNameExists(
+            final PwmRequest pwmRequest,
+            final String rdnValue
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException
+    {
+        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
+        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
+        searchConfiguration.setUsername(rdnValue);
+        try {
+            final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
+                    searchConfiguration, 2, Collections.<String>emptyList());
+            return results != null && !results.isEmpty();
+        } catch (PwmOperationalException e) {
+            final String msg = "ldap error while searching for duplicate entry names: " + e.getMessage();
+            NewUserUtils.LOGGER.error(pwmRequest, msg);
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NEW_USER_FAILURE, msg));
+        }
+    }
+
+    private static void sendNewUserEmailConfirmation(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException
+    {
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        final UserInfoBean userInfoBean = pwmSession.getUserInfoBean();
+        final Configuration config = pwmRequest.getConfig();
+        final Locale locale = pwmSession.getSessionStateBean().getLocale();
+        final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(PwmSetting.EMAIL_NEWUSER, locale);
+
+        if (configuredEmailSetting == null) {
+            NewUserUtils.LOGGER.debug(pwmSession,
+                    "skipping send of new user email for '" + userInfoBean.getUserIdentity().getUserDN() + "' no email configured");
+            return;
+        }
+
+        pwmRequest.getPwmApplication().getEmailQueue().submitEmail(
+                configuredEmailSetting,
+                pwmSession.getUserInfoBean(),
+                pwmSession.getSessionManager().getMacroMachine(pwmRequest.getPwmApplication())
+        );
+    }
+
+    static MacroMachine createMacroMachineForNewUser(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final NewUserBean.NewUserForm newUserForm
+    )
+            throws PwmUnrecoverableException
+    {
+        final Map<String, String> formValues = newUserForm.getFormData();
+        final UserInfoBean stubUserBean = new UserInfoBean();
+
+        final String emailAddressAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(
+                PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE);
+        stubUserBean.setUserEmailAddress(formValues.get(emailAddressAttribute));
+
+        final String usernameAttribute = pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.LDAP_USERNAME_ATTRIBUTE);
+        stubUserBean.setUsername(formValues.get(usernameAttribute));
+
+        final LoginInfoBean stubLoginBean = new LoginInfoBean();
+        stubLoginBean.setUserCurrentPassword(newUserForm.getNewUserPassword());
+
+        final UserDataReader stubReader = new NewUserUserDataReader(formValues);
+        return new MacroMachine(pwmApplication, sessionLabel, stubUserBean, stubLoginBean, stubReader);
+    }
+
+    static void initializeToken(
+            final PwmRequest pwmRequest,
+            final NewUserBean newUserBean,
+            final TokenVerificationProgress.TokenChannel tokenType
+    )
+            throws PwmUnrecoverableException
+    {
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+
+        if (pwmApplication.getConfig().getTokenStorageMethod() == TokenStorageMethod.STORE_LDAP) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{
+                    "cannot generate new user tokens when storage type is configured as STORE_LDAP.",
+            }));
+        }
+
+        final Configuration config = pwmApplication.getConfig();
+        final Map<String, String> tokenPayloadMap = NewUserFormUtils.toTokenPayload(pwmRequest, newUserBean.getNewUserForm());
+        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmApplication, pwmRequest.getSessionLabel(), newUserBean.getNewUserForm());
+
+        switch (tokenType) {
+            case SMS: {
+                final String toNum = tokenPayloadMap.get(pwmApplication.getConfig().getDefaultLdapProfile().readSettingAsString(PwmSetting.SMS_USER_PHONE_ATTRIBUTE));
+
+                final RestTokenDataClient.TokenDestinationData inputTokenDestData = new RestTokenDataClient.TokenDestinationData(
+                        null, toNum, null);
+                final RestTokenDataClient restTokenDataClient = new RestTokenDataClient(pwmApplication);
+                final RestTokenDataClient.TokenDestinationData outputDestTokenData = restTokenDataClient.figureDestTokenDisplayString(
+                        pwmRequest.getSessionLabel(),
+                        inputTokenDestData,
+                        null,
+                        pwmRequest.getLocale());
+
+                final String tokenKey;
+                try {
+                    final TokenPayload tokenPayload = pwmApplication.getTokenService().createTokenPayload(
+                            password.pwm.svc.token.TokenType.NEWUSER_SMS,
+                            tokenPayloadMap,
+                            null,
+                            Collections.singleton(outputDestTokenData.getSms())
+                    );
+                    tokenKey = pwmApplication.getTokenService().generateNewToken(tokenPayload,
+                            pwmRequest.getSessionLabel());
+                } catch (PwmOperationalException e) {
+                    throw new PwmUnrecoverableException(e.getErrorInformation());
+                }
+
+                final String message = config.readSettingAsLocalizedString(PwmSetting.SMS_NEWUSER_TOKEN_TEXT,
+                        pwmSession.getSessionStateBean().getLocale());
+
+                try {
+                    TokenService.TokenSender.sendSmsToken(pwmApplication, null, macroMachine,
+                            outputDestTokenData.getSms(), message, tokenKey);
+                } catch (Exception e) {
+                    throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
+                }
+
+                newUserBean.getTokenVerificationProgress().getIssuedTokens().add(TokenVerificationProgress.TokenChannel.SMS);
+                newUserBean.getTokenVerificationProgress().setTokenDisplayText(outputDestTokenData.getDisplayValue());
+                newUserBean.getTokenVerificationProgress().setPhase(TokenVerificationProgress.TokenChannel.SMS);
+            }
+            break;
+
+            case EMAIL: {
+                final EmailItemBean configuredEmailSetting = config.readSettingAsEmail(
+                        PwmSetting.EMAIL_NEWUSER_VERIFICATION, pwmSession.getSessionStateBean().getLocale());
+                final String toAddress = macroMachine.expandMacros(configuredEmailSetting.getTo());
+
+                final RestTokenDataClient.TokenDestinationData inputTokenDestData = new RestTokenDataClient.TokenDestinationData(
+                        toAddress, null, null);
+                final RestTokenDataClient restTokenDataClient = new RestTokenDataClient(pwmApplication);
+                final RestTokenDataClient.TokenDestinationData outputDestTokenData = restTokenDataClient.figureDestTokenDisplayString(
+                        pwmRequest.getSessionLabel(),
+                        inputTokenDestData,
+                        null,
+                        pwmRequest.getLocale());
+
+                final String tokenKey;
+                try {
+                    final TokenPayload tokenPayload = pwmApplication.getTokenService().createTokenPayload(
+                            password.pwm.svc.token.TokenType.NEWUSER_EMAIL,
+                            tokenPayloadMap,
+                            null,
+                            Collections.singleton(outputDestTokenData.getEmail())
+                    );
+                    tokenKey = pwmApplication.getTokenService().generateNewToken(tokenPayload,
+                            pwmRequest.getSessionLabel());
+                } catch (PwmOperationalException e) {
+                    throw new PwmUnrecoverableException(e.getErrorInformation());
+                }
+
+                newUserBean.getTokenVerificationProgress().getIssuedTokens().add(TokenVerificationProgress.TokenChannel.EMAIL);
+                newUserBean.getTokenVerificationProgress().setPhase(TokenVerificationProgress.TokenChannel.EMAIL);
+                newUserBean.getTokenVerificationProgress().setTokenDisplayText(outputDestTokenData.getDisplayValue());
+
+                final EmailItemBean emailItemBean = new EmailItemBean(
+                        outputDestTokenData.getEmail(),
+                        configuredEmailSetting.getFrom(),
+                        configuredEmailSetting.getSubject(),
+                        configuredEmailSetting.getBodyPlain().replace("%TOKEN%", tokenKey),
+                        configuredEmailSetting.getBodyHtml().replace("%TOKEN%", tokenKey));
+
+                try {
+                    TokenService.TokenSender.sendEmailToken(pwmApplication, null, macroMachine, emailItemBean,
+                            outputDestTokenData.getEmail(), tokenKey);
+                } catch (Exception e) {
+                    throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN));
+                }
+            }
+            break;
+
+            default:
+                newUserBean.getTokenVerificationProgress().setPhase(null);
+                JavaHelper.unhandledSwitchStatement(tokenType);
+        }
+    }
+}