소스 검색

configguide improvements

Jason Rivard 7 년 전
부모
커밋
d3a08bb12e

+ 13 - 2
server/src/main/java/password/pwm/http/HttpHeader.java

@@ -24,6 +24,10 @@ package password.pwm.http;
 
 import password.pwm.PwmConstants;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
 public enum HttpHeader {
     Accept("Accept"),
     Connection("Connection"),
@@ -40,7 +44,7 @@ public enum HttpHeader {
     Content_Language("Content-Language"),
     Accept_Encoding("Accept-Encoding"),
     Accept_Language("Accept-Language"),
-    Authorization("Authorization"),
+    Authorization("Authorization", Flag.Sensitive),
     UserAgent("User-Agent"),
     Referer("Referer"),
     Origin("Origin"),
@@ -60,11 +64,18 @@ public enum HttpHeader {
 
     ;
 
+    enum Flag {
+        Sensitive
+    }
+
+
+    private final Collection<Flag> flags;
     private final String httpName;
 
-    HttpHeader(final String httpName)
+    HttpHeader(final String httpName, final Flag... flags)
     {
         this.httpName = httpName;
+        this.flags = Collections.unmodifiableList(flags == null ? Collections.emptyList() : Arrays.asList(flags));
     }
 
     public String getHttpName()

+ 2 - 4
server/src/main/java/password/pwm/http/bean/ConfigGuideBean.java

@@ -22,8 +22,7 @@
 
 package password.pwm.http.bean;
 
-import lombok.Getter;
-import lombok.Setter;
+import lombok.Data;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.value.FileValue;
 import password.pwm.http.servlet.configguide.ConfigGuideForm;
@@ -37,8 +36,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-@Getter
-@Setter
+@Data
 public class ConfigGuideBean extends PwmSessionBean {
 
     private GuideStep step = GuideStep.START;

+ 48 - 12
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java

@@ -25,7 +25,6 @@ package password.pwm.http.servlet.configguide;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.StoredValue;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.FileValue;
@@ -34,9 +33,11 @@ import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.UserPermissionValue;
 import password.pwm.config.value.X509CertificateValue;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.ConfigGuideBean;
 import password.pwm.util.PasswordData;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import java.net.URI;
@@ -62,6 +63,22 @@ public class ConfigGuideForm {
     }
 
 
+    private static void updateStoredConfigTemplateValue(
+            final Map<ConfigGuideFormField, String> formData,
+            final StoredConfigurationImpl storedConfiguration,
+            final PwmSetting pwmSetting,
+            final ConfigGuideFormField formField,
+            final PwmSettingTemplate.Type type
+    )
+            throws PwmUnrecoverableException
+    {
+        final String formValue = formData.get(formField);
+        if (!StringUtil.isEmpty(formValue)) {
+            final PwmSettingTemplate template = PwmSettingTemplate.templateForString(formValue, type);
+            storedConfiguration.writeSetting(pwmSetting, null, new StringValue(template.toString()), null);
+        }
+    }
+
     public static StoredConfigurationImpl generateStoredConfig(
             final ConfigGuideBean configGuideBean
     )
@@ -73,15 +90,26 @@ public class ConfigGuideForm {
         final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.newStoredConfiguration();
 
         // templates
-        storedConfiguration.writeSetting(PwmSetting.TEMPLATE_LDAP, null, new StringValue(
-                PwmSettingTemplate.templateForString(formData.get(ConfigGuideFormField.PARAM_TEMPLATE_LDAP), PwmSettingTemplate.Type.LDAP_VENDOR).toString()
-        ), null);
-        storedConfiguration.writeSetting(PwmSetting.TEMPLATE_STORAGE, null, new StringValue(
-                PwmSettingTemplate.templateForString(formData.get(ConfigGuideFormField.PARAM_TEMPLATE_STORAGE), PwmSettingTemplate.Type.STORAGE).toString()
-        ), null);
-        storedConfiguration.writeSetting(PwmSetting.DB_VENDOR_TEMPLATE, null, new StringValue(
-                PwmSettingTemplate.templateForString(formData.get(ConfigGuideFormField.PARAM_DB_VENDOR), PwmSettingTemplate.Type.DB_VENDOR).toString()
-        ), null);
+        updateStoredConfigTemplateValue(
+                formData,
+                storedConfiguration,
+                PwmSetting.TEMPLATE_LDAP,
+                ConfigGuideFormField.PARAM_TEMPLATE_LDAP,
+                PwmSettingTemplate.Type.LDAP_VENDOR);
+
+        updateStoredConfigTemplateValue(
+                formData,
+                storedConfiguration,
+                PwmSetting.TEMPLATE_STORAGE,
+                ConfigGuideFormField.PARAM_TEMPLATE_STORAGE,
+                PwmSettingTemplate.Type.STORAGE);
+
+        updateStoredConfigTemplateValue(
+                formData,
+                storedConfiguration,
+                PwmSetting.DB_VENDOR_TEMPLATE,
+                ConfigGuideFormField.PARAM_DB_VENDOR,
+                PwmSettingTemplate.Type.DB_VENDOR);
 
         // establish a default ldap profile
         storedConfiguration.writeSetting(PwmSetting.LDAP_PROFILE_LIST, null, new StringArrayValue(Collections.singletonList(LDAP_PROFILE_NAME)), null);
@@ -113,8 +141,13 @@ public class ConfigGuideForm {
         }
 
         {
-            final String ldapTestUserDN = formData.get(ConfigGuideFormField.PARAM_LDAP_TEST_USER);
-            storedConfiguration.writeSetting(PwmSetting.LDAP_TEST_USER_DN, LDAP_PROFILE_NAME, new StringValue(ldapTestUserDN), null);
+            final boolean testuserEnabled = Boolean.parseBoolean(formData.get(ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED));
+            if (testuserEnabled) {
+                final String ldapTestUserDN = formData.get(ConfigGuideFormField.PARAM_LDAP_TEST_USER);
+                storedConfiguration.writeSetting(PwmSetting.LDAP_TEST_USER_DN, LDAP_PROFILE_NAME, new StringValue(ldapTestUserDN), null);
+            } else {
+                storedConfiguration.resetSetting(PwmSetting.LDAP_TEST_USER_DN, LDAP_PROFILE_NAME, null);
+            }
         }
 
         {  // set admin query
@@ -155,6 +188,9 @@ public class ConfigGuideForm {
         // set site url
         storedConfiguration.writeSetting(PwmSetting.PWM_SITE_URL, new StringValue(formData.get(ConfigGuideFormField.PARAM_APP_SITEURL)), null);
 
+        // enable debug mode
+        storedConfiguration.writeSetting(PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS, null, new BooleanValue(true), null);
+
         return storedConfiguration;
     }
 

+ 1 - 0
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideFormField.java

@@ -37,6 +37,7 @@ public enum ConfigGuideFormField {
     PARAM_LDAP_PROXY_PW(PwmSetting.LDAP_PROXY_USER_PASSWORD),
 
     PARAM_LDAP_CONTEXT(PwmSetting.LDAP_CONTEXTLESS_ROOT),
+    PARAM_LDAP_TEST_USER_ENABLED(null),
     PARAM_LDAP_TEST_USER(PwmSetting.LDAP_TEST_USER_DN),
     PARAM_LDAP_ADMIN_GROUP(PwmSetting.QUERY_MATCH_PWM_ADMIN),
 

+ 89 - 276
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -24,11 +24,6 @@ package password.pwm.http.servlet.configguide;
 
 import com.google.gson.reflect.TypeToken;
 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 org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
@@ -39,7 +34,6 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.function.UserMatchViewerFunction;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.stored.ConfigurationProperty;
-import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.FileValue;
 import password.pwm.error.ErrorInformation;
@@ -56,39 +50,29 @@ import password.pwm.health.HealthTopic;
 import password.pwm.health.LDAPStatusChecker;
 import password.pwm.http.ContextManager;
 import password.pwm.http.HttpMethod;
+import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmRequestAttribute;
-import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.ConfigGuideBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.configeditor.ConfigEditorServlet;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapBrowser;
-import password.pwm.ldap.schema.SchemaManager;
 import password.pwm.ldap.schema.SchemaOperationResult;
-import password.pwm.util.LDAPPermissionCalculator;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
-import password.pwm.util.java.Percent;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.X509Utils;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.bean.HealthData;
 
-import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.Serializable;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.SocketAddress;
 import java.net.URI;
 import java.time.Instant;
 import java.util.ArrayList;
@@ -106,14 +90,12 @@ import java.util.Map;
                 PwmConstants.URL_PREFIX_PRIVATE + "/config/ConfigGuide"
         }
 )
-public class ConfigGuideServlet extends AbstractPwmServlet {
+public class ConfigGuideServlet extends ControlledPwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.getLogger(ConfigGuideServlet.class.getName());
 
-
     private static final String LDAP_PROFILE_KEY = "default";
 
-
     public enum ConfigGuideAction implements AbstractPwmServlet.ProcessAction {
         ldapHealth(HttpMethod.GET),
         updateForm(HttpMethod.POST),
@@ -141,22 +123,23 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         }
     }
 
-    protected ConfigGuideAction readProcessAction(final PwmRequest request)
-            throws PwmUnrecoverableException
-    {
-        try {
-            return ConfigGuideAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+    @Override
+    public Class<? extends ProcessAction> getProcessActionsClass() {
+        return ConfigGuideAction.class;
     }
 
+    private ConfigGuideBean getBean(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
+        return pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, ConfigGuideBean.class);
+    }
 
     @Override
-    protected void processAction(final PwmRequest pwmRequest)
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
+    protected void nextStep(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException {
+        ConfigGuideUtils.forwardToJSP(pwmRequest);
+    }
+
+    @Override
+    public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException
     {
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
 
         if (pwmApplication.getSessionStateService().getBean(pwmRequest, ConfigGuideBean.class).getStep() == GuideStep.START) {
@@ -167,9 +150,8 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
 
         if (pwmApplication.getApplicationMode() != PwmApplicationMode.NEW) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,"ConfigGuide unavailable unless in NEW mode");
-            LOGGER.error(pwmSession, errorInformation.toDebugStr());
-            pwmRequest.respondWithError(errorInformation);
-            return;
+            LOGGER.error(pwmRequest, errorInformation.toDebugStr());
+            throw new PwmUnrecoverableException(errorInformation);
         }
 
         if (!configGuideBean.getFormData().containsKey(ConfigGuideFormField.PARAM_APP_SITEURL)) {
@@ -195,121 +177,38 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             }
         }
 
-        final ConfigGuideAction action = readProcessAction(pwmRequest);
-        if (action != null) {
-            pwmRequest.validatePwmFormID();
-            switch (action) {
-                case ldapHealth:
-                    restLdapHealth(pwmRequest, configGuideBean);
-                    return;
-
-                case updateForm:
-                    restUpdateLdapForm(pwmRequest, configGuideBean);
-                    return;
-
-                case gotoStep:
-                    restGotoStep(pwmRequest, configGuideBean);
-                    return;
-
-                case useConfiguredCerts:
-                    restUseConfiguredCerts(pwmRequest, configGuideBean);
-                    return;
-
-                case uploadConfig:
-                    restUploadConfig(pwmRequest);
-                    return;
-
-                case extendSchema:
-                    restExtendSchema(pwmRequest, configGuideBean);
-                    return;
-
-                case viewAdminMatches:
-                    restViewAdminMatches(pwmRequest, configGuideBean);
-                    return;
-
-                case browseLdap:
-                    restBrowseLdap(pwmRequest, configGuideBean);
-                    return;
-
-                case uploadJDBCDriver:
-                    restUploadJDBCDriver(pwmRequest, configGuideBean);
-                    return;
-
-                case skipGuide:
-                    restSkipGuide(pwmRequest);
-                    return;
-
-                default:
-                    JavaHelper.unhandledSwitchStatement(action);
-            }
-        }
-
-        if (!pwmRequest.getPwmResponse().getHttpServletResponse().isCommitted()) {
-            forwardToJSP(pwmRequest);
-        }
+        return ProcessStatus.Continue;
     }
 
-    public static void restUploadConfig(final PwmRequest pwmRequest)
+    @ActionHandler(action = "uploadConfig")
+    private ProcessStatus restUploadConfig(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ServletException
     {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final HttpServletRequest req = pwmRequest.getHttpServletRequest();
-
-        if (pwmApplication.getApplicationMode() == PwmApplicationMode.RUNNING) {
-            final String errorMsg = "config upload is not permitted when in running mode";
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_UPLOAD_FAILURE,errorMsg,new String[]{errorMsg});
-            pwmRequest.respondWithError(errorInformation, true);
-            return;
-        }
-
-        if (ServletFileUpload.isMultipartContent(req)) {
-            final InputStream uploadedFile = pwmRequest.readFileUploadStream(PwmConstants.PARAM_FILE_UPLOAD);
-            if (uploadedFile != null) {
-                try {
-                    final StoredConfigurationImpl storedConfig = StoredConfigurationImpl.fromXml(uploadedFile);
-                    final List<String> configErrors = storedConfig.validateValues();
-                    if (configErrors != null && !configErrors.isEmpty()) {
-                        throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,configErrors.get(0)));
-                    }
-                    writeConfig(ContextManager.getContextManager(req.getSession()),storedConfig);
-                    LOGGER.trace(pwmSession, "read config from file: " + storedConfig.toString());
-                    final RestResultBean restResultBean = RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown);
-                    pwmRequest.getPwmResponse().outputJsonResult(restResultBean);
-                    req.getSession().invalidate();
-                } catch (PwmException e) {
-                    final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
-                    pwmRequest.getPwmResponse().outputJsonResult(restResultBean);
-                    LOGGER.error(pwmSession, e.getErrorInformation().toDebugStr());
-                }
-            } else {
-                final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_UPLOAD_FAILURE, "error reading config file: no file present in upload");
-                final RestResultBean restResultBean = RestResultBean.fromError(errorInformation, pwmRequest);
-                pwmRequest.getPwmResponse().outputJsonResult(restResultBean);
-                LOGGER.error(pwmSession, errorInformation.toDebugStr());
-            }
-        }
+        ConfigGuideUtils.restUploadConfig(pwmRequest);
+        return ProcessStatus.Halt;
     }
 
-
-    private void restUseConfiguredCerts(
-            final PwmRequest pwmRequest,
-            final ConfigGuideBean configGuideBean
+    @ActionHandler(action = "useConfiguredCerts")
+    private ProcessStatus restUseConfiguredCerts(
+            final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException, IOException
     {
+        final ConfigGuideBean configGuideBean = getBean(pwmRequest);
+
         final boolean value = Boolean.parseBoolean(pwmRequest.readParameterAsString("value"));
         configGuideBean.setUseConfiguredCerts(value);
         pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
+        return ProcessStatus.Halt;
     }
 
-
-    private void restLdapHealth(
-            final PwmRequest pwmRequest,
-            final ConfigGuideBean configGuideBean
+    @ActionHandler(action = "ldapHealth")
+    private ProcessStatus restLdapHealth(
+            final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException
     {
+        final ConfigGuideBean configGuideBean = getBean(pwmRequest);
 
         final StoredConfigurationImpl storedConfigurationImpl = ConfigGuideForm.generateStoredConfig(configGuideBean);
         final Configuration tempConfiguration = new Configuration(storedConfigurationImpl);
@@ -324,7 +223,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         switch (configGuideBean.getStep()) {
             case LDAP_SERVER: {
                 try {
-                    checkLdapServer(configGuideBean);
+                    ConfigGuideUtils.checkLdapServer(configGuideBean);
                     records.add(password.pwm.health.HealthRecord.forMessage(HealthMessage.LDAP_OK));
                 } catch (Exception e) {
                     records.add(new HealthRecord(HealthStatus.WARN, HealthTopic.LDAP, "Can not connect to remote server: " + e.getMessage()));
@@ -400,20 +299,16 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         jsonOutput.overall = HealthMonitor.getMostSevereHealthStatus(records).toString();
         final RestResultBean restResultBean = RestResultBean.withData(jsonOutput);
         pwmRequest.outputJsonResult(restResultBean);
+        return ProcessStatus.Halt;
     }
 
-    public static Percent stepProgress(final GuideStep step) {
-        final int ordinal = step.ordinal();
-        final int total = GuideStep.values().length - 2;
-        return new Percent(ordinal,total);
-    }
-
-    private void restViewAdminMatches(
-            final PwmRequest pwmRequest,
-            final ConfigGuideBean configGuideBean
+    @ActionHandler(action = "viewAdminMatches")
+    private ProcessStatus restViewAdminMatches(
+            final PwmRequest pwmRequest
     )
-            throws IOException, ServletException
+            throws IOException, ServletException, PwmUnrecoverableException
     {
+        final ConfigGuideBean configGuideBean = getBean(pwmRequest);
 
         try {
             final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
@@ -422,20 +317,23 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             pwmRequest.outputJsonResult(RestResultBean.withData(output));
         } catch (PwmException e) {
             LOGGER.error(pwmRequest,e.getErrorInformation());
-            pwmRequest.respondWithError(e.getErrorInformation());
+            pwmRequest.respondWithError(e.getErrorInformation(), false);
         } catch (Exception e) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, "error while testing matches = " + e.getMessage());
             LOGGER.error(pwmRequest,errorInformation);
             pwmRequest.respondWithError(errorInformation);
         }
+        return ProcessStatus.Halt;
     }
 
-    private void restBrowseLdap(
-            final PwmRequest pwmRequest,
-            final ConfigGuideBean configGuideBean
+    @ActionHandler(action = "browseLdap")
+    private ProcessStatus restBrowseLdap(
+            final PwmRequest pwmRequest
     )
             throws IOException, ServletException, PwmUnrecoverableException
     {
+        final ConfigGuideBean configGuideBean = getBean(pwmRequest);
+
         final StoredConfigurationImpl storedConfiguration = ConfigGuideForm.generateStoredConfig(configGuideBean);
         if (configGuideBean.getStep() == GuideStep.LDAP_PROXY) {
             storedConfiguration.resetSetting(PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_KEY, null);
@@ -445,7 +343,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         final Instant startTime = Instant.now();
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
         final String profile = inputMap.get("profile");
-        final String dn = inputMap.containsKey("dn") ? inputMap.get("dn") : "";
+        final String dn = inputMap.getOrDefault("dn", "");
 
         final LdapBrowser ldapBrowser = new LdapBrowser(storedConfiguration);
         final LdapBrowser.LdapBrowseResult result = ldapBrowser.doBrowse(profile, dn);
@@ -456,14 +354,18 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
                 + ", result=" + JsonUtil.serialize(result));
 
         pwmRequest.outputJsonResult(RestResultBean.withData(result));
+
+        return ProcessStatus.Halt;
     }
 
-    private void restUpdateLdapForm(
-            final PwmRequest pwmRequest,
-            final ConfigGuideBean configGuideBean
+    @ActionHandler(action = "updateForm")
+    private ProcessStatus restUpdateForm(
+            final PwmRequest pwmRequest
     )
             throws IOException, PwmUnrecoverableException
     {
+        final ConfigGuideBean configGuideBean = getBean(pwmRequest);
+
         final String bodyString = pwmRequest.readRequestBodyAsString();
         final Map<ConfigGuideFormField,String> incomingFormData = JsonUtil.deserialize(bodyString, new TypeToken<Map<ConfigGuideFormField, String>>() {
         });
@@ -473,58 +375,55 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         }
 
         pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
+
+        return ProcessStatus.Halt;
     }
 
-    private void restGotoStep(final PwmRequest pwmRequest, final ConfigGuideBean configGuideBean)
+    @ActionHandler(action = "gotoStep")
+    private ProcessStatus restGotoStep(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ServletException
     {
+        final ConfigGuideBean configGuideBean = getBean(pwmRequest);
+
         final String requestedStep = pwmRequest.readParameterAsString("step");
-        GuideStep step = null;
+        GuideStep step = GuideStep.START;
         if (requestedStep != null && requestedStep.length() > 0) {
             try {
                 step = GuideStep.valueOf(requestedStep);
-            } catch (IllegalArgumentException e) { /* */ }
+            } catch (IllegalArgumentException e) {
+                final String errorMsg = "unknown goto step request: " + requestedStep;
+                LOGGER.error(pwmRequest, errorMsg);
+            }
         }
 
-        if (GuideStep.START.equals(GuideStep.valueOf(requestedStep))) {
+        if (step == GuideStep.START) {
             configGuideBean.getFormData().clear();
             configGuideBean.getFormData().putAll(ConfigGuideForm.defaultForm());
-        }
-
-        if ("NEXT".equals(requestedStep)) {
+        } else if (step == GuideStep.NEXT) {
             step = configGuideBean.getStep().next();
             while (step != GuideStep.FINISH && !step.visible(configGuideBean)) {
                 step =step.next();
             }
-        } else if ("PREVIOUS".equals(requestedStep)) {
+        } else if (step == GuideStep.PREVIOUS) {
             step = configGuideBean.getStep().previous();
             while (step != GuideStep.START && !step.visible(configGuideBean)) {
                 step = step.previous();
             }
         }
 
-        if (step == null) {
-            final String errorMsg = "unknown goto step request: " + requestedStep;
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg);
-            final RestResultBean restResultBean = RestResultBean.fromError(errorInformation, pwmRequest);
-            LOGGER.error(pwmRequest,errorInformation.toDebugStr());
-            pwmRequest.outputJsonResult(restResultBean);
-            return;
-        }
-
         if (step == GuideStep.FINISH) {
             final ContextManager contextManager = ContextManager.getContextManager(pwmRequest);
             try {
-                writeConfig(contextManager, configGuideBean);
+                ConfigGuideUtils.writeConfig(contextManager, configGuideBean);
                 pwmRequest.getPwmSession().getSessionStateBean().setTheme(null);
             } catch (PwmException e) {
                 final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
                 pwmRequest.outputJsonResult(restResultBean);
-                return;
+                return ProcessStatus.Halt;
             } catch (Exception e) {
                 final RestResultBean restResultBean = RestResultBean.fromError(new ErrorInformation(PwmError.ERROR_UNKNOWN,"error during save: " + e.getMessage()), pwmRequest);
                 pwmRequest.outputJsonResult(restResultBean);
-                return;
+                return ProcessStatus.Halt;
             }
             final HashMap<String,String> resultData = new HashMap<>();
             resultData.put("serverRestart","true");
@@ -535,125 +434,34 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
             LOGGER.trace("setting current step to: " + step);
         }
-    }
-
 
-
-    private void writeConfig(
-            final ContextManager contextManager,
-            final ConfigGuideBean configGuideBean
-    ) throws PwmOperationalException, PwmUnrecoverableException {
-        final StoredConfigurationImpl storedConfiguration = ConfigGuideForm.generateStoredConfig(configGuideBean);
-        final String configPassword = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_CONFIG_PASSWORD);
-        if (configPassword != null && configPassword.length() > 0) {
-            storedConfiguration.setPassword(configPassword);
-        } else {
-            storedConfiguration.writeConfigProperty(ConfigurationProperty.PASSWORD_HASH, null);
-        }
-
-        storedConfiguration.writeConfigProperty(ConfigurationProperty.CONFIG_IS_EDITABLE, "false");
-        writeConfig(contextManager, storedConfiguration);
+        return ProcessStatus.Continue;
     }
 
-    private static void writeConfig(
-            final ContextManager contextManager,
-            final StoredConfigurationImpl storedConfiguration
-    ) throws PwmOperationalException, PwmUnrecoverableException {
-        final ConfigurationReader configReader = contextManager.getConfigReader();
-        final PwmApplication pwmApplication = contextManager.getPwmApplication();
-
-        try {
-            // add a random security key
-            storedConfiguration.initNewRandomSecurityKey();
-
-            configReader.saveConfiguration(storedConfiguration, pwmApplication, null);
-
-            contextManager.requestPwmApplicationRestart();
-        } catch (PwmException e) {
-            throw new PwmOperationalException(e.getErrorInformation());
-        } catch (Exception e) {
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,"unable to save configuration: " + e.getLocalizedMessage());
-            throw new PwmOperationalException(errorInformation);
-        }
-    }
-
-
-    static void forwardToJSP(
-            final PwmRequest pwmRequest
-    )
-            throws IOException, ServletException, PwmUnrecoverableException
+    @ActionHandler(action = "extendSchema")
+    private ProcessStatus restExtendSchema(final PwmRequest pwmRequest)
+            throws IOException, PwmUnrecoverableException
     {
-        final ConfigGuideBean configGuideBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest,ConfigGuideBean.class);
-
-        if (configGuideBean.getStep() == GuideStep.LDAP_PERMISSIONS) {
-            final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator(ConfigGuideForm.generateStoredConfig(configGuideBean));
-            pwmRequest.setAttribute(PwmRequestAttribute.LdapPermissionItems, ldapPermissionCalculator);
-        }
-
-        final HttpServletRequest req = pwmRequest.getHttpServletRequest();
-        final ServletContext servletContext = req.getSession().getServletContext();
-        String destURL = '/' + PwmConstants.URL_JSP_CONFIG_GUIDE;
-        destURL = destURL.replace("%1%", configGuideBean.getStep().toString().toLowerCase());
-        servletContext.getRequestDispatcher(destURL).forward(req, pwmRequest.getPwmResponse().getHttpServletResponse());
-    }
-
-    public static SchemaOperationResult extendSchema(final ConfigGuideBean configGuideBean, final boolean doSchemaExtension) {
-        final Map<ConfigGuideFormField,String> form = configGuideBean.getFormData();
-        final boolean ldapServerSecure = "true".equalsIgnoreCase(form.get(ConfigGuideFormField.PARAM_LDAP_SECURE));
-        final String ldapUrl = "ldap" + (ldapServerSecure ? "s" : "") + "://" + form.get(ConfigGuideFormField.PARAM_LDAP_HOST) + ":" + form.get(ConfigGuideFormField.PARAM_LDAP_PORT);
-        try {
-            final ChaiConfiguration chaiConfiguration = new ChaiConfiguration(ldapUrl, form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_DN), form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_PW));
-            chaiConfiguration.setSetting(ChaiSetting.PROMISCUOUS_SSL,"true");
-            final ChaiProvider chaiProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
-            if (doSchemaExtension) {
-                return SchemaManager.extendSchema(chaiProvider);
-            } else {
-                return SchemaManager.checkExistingSchema(chaiProvider);
-            }
-        } catch (Exception e) {
-            LOGGER.error("unable to create schema extender object: " + e.getMessage());
-            return null;
-        }
-    }
+        final ConfigGuideBean configGuideBean = getBean(pwmRequest);
 
-    private void restExtendSchema(final PwmRequest pwmRequest, final ConfigGuideBean configGuideBean)
-            throws IOException
-    {
         try {
-            final SchemaOperationResult schemaOperationResult = extendSchema(configGuideBean, true);
+            final SchemaOperationResult schemaOperationResult = ConfigGuideUtils.extendSchema(configGuideBean, true);
             pwmRequest.outputJsonResult(RestResultBean.withData(schemaOperationResult.getOperationLog()));
         } catch (Exception e) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,e.getMessage());
             pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
             LOGGER.error(pwmRequest, e.getMessage(), e);
         }
-    }
-
-
-
-    private void checkLdapServer(final ConfigGuideBean configGuideBean) throws PwmOperationalException, IOException {
-        final Map<ConfigGuideFormField,String> formData = configGuideBean.getFormData();
-        final String host = formData.get(ConfigGuideFormField.PARAM_LDAP_HOST);
-        final int port = Integer.parseInt(formData.get(ConfigGuideFormField.PARAM_LDAP_PORT));
-
-        { // socket test
-            final InetAddress inetAddress = InetAddress.getByName(host);
-            final SocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
-            final Socket socket = new Socket();
-
-            final int timeout = 2000;
-            socket.connect(socketAddress, timeout);
-        }
 
-        if (Boolean.parseBoolean(formData.get(ConfigGuideFormField.PARAM_LDAP_SECURE))) {
-            X509Utils.readRemoteCertificates(host, port);
-        }
+        return ProcessStatus.Halt;
     }
 
-    static void restUploadJDBCDriver(final PwmRequest pwmRequest, final ConfigGuideBean configGuideBean)
+    @ActionHandler(action = "uploadJDBCDriver")
+    private ProcessStatus restUploadJDBCDriver(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException, IOException, ServletException
     {
         try {
+            final ConfigGuideBean configGuideBean = getBean(pwmRequest);
             final int maxFileSize = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_JDBC_JAR_SIZE));
             final FileValue fileValue = ConfigEditorServlet.readFileUploadToSettingValue(pwmRequest, maxFileSize);
             configGuideBean.setDatabaseDriver(fileValue);
@@ -664,9 +472,13 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             pwmRequest.getPwmResponse().outputJsonResult(restResultBean);
             LOGGER.error(pwmRequest, e.getErrorInformation().toDebugStr());
         }
+
+        return ProcessStatus.Halt;
     }
 
-    private void restSkipGuide(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException {
+    @ActionHandler(action = "skipGuide")
+    private ProcessStatus restSkipGuide(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException
+    {
         final Map<String,String> inputJson = pwmRequest.readBodyAsJsonStringMap(PwmHttpRequestWrapper.Flag.BypassValidation);
         final String password = inputJson.get("password");
         final ContextManager contextManager = ContextManager.getContextManager(pwmRequest);
@@ -674,14 +486,15 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             final StoredConfigurationImpl storedConfiguration = new StoredConfigurationImpl();
             storedConfiguration.writeConfigProperty(ConfigurationProperty.CONFIG_IS_EDITABLE, "true");
             storedConfiguration.setPassword(password);
-            writeConfig(contextManager, storedConfiguration);
+            ConfigGuideUtils.writeConfig(contextManager, storedConfiguration);
             pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
             pwmRequest.invalidateSession();
         } catch (PwmOperationalException e) {
             LOGGER.error("error during skip config guide: " + e.getMessage(),e);
         }
-    }
 
+        return ProcessStatus.Halt;
+    }
 }
 
 

+ 193 - 0
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java

@@ -0,0 +1,193 @@
+package password.pwm.http.servlet.configguide;
+
+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 org.apache.commons.fileupload.servlet.ServletFileUpload;
+import password.pwm.PwmApplication;
+import password.pwm.PwmApplicationMode;
+import password.pwm.PwmConstants;
+import password.pwm.config.stored.ConfigurationProperty;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ContextManager;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmRequestAttribute;
+import password.pwm.http.PwmSession;
+import password.pwm.http.bean.ConfigGuideBean;
+import password.pwm.i18n.Message;
+import password.pwm.ldap.schema.SchemaManager;
+import password.pwm.ldap.schema.SchemaOperationResult;
+import password.pwm.util.LDAPPermissionCalculator;
+import password.pwm.util.java.Percent;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.X509Utils;
+import password.pwm.ws.server.RestResultBean;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.List;
+import java.util.Map;
+
+public class ConfigGuideUtils {
+
+    private static final PwmLogger LOGGER = PwmLogger.getLogger(ConfigGuideUtils.class.getName());
+
+    static void writeConfig(
+            final ContextManager contextManager,
+            final ConfigGuideBean configGuideBean
+    ) throws PwmOperationalException, PwmUnrecoverableException {
+        final StoredConfigurationImpl storedConfiguration = ConfigGuideForm.generateStoredConfig(configGuideBean);
+        final String configPassword = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_CONFIG_PASSWORD);
+        if (configPassword != null && configPassword.length() > 0) {
+            storedConfiguration.setPassword(configPassword);
+        } else {
+            storedConfiguration.writeConfigProperty(ConfigurationProperty.PASSWORD_HASH, null);
+        }
+
+        storedConfiguration.writeConfigProperty(ConfigurationProperty.CONFIG_IS_EDITABLE, "false");
+        ConfigGuideUtils.writeConfig(contextManager, storedConfiguration);
+    }
+
+    static void writeConfig(
+            final ContextManager contextManager,
+            final StoredConfigurationImpl storedConfiguration
+    ) throws PwmOperationalException, PwmUnrecoverableException {
+        final ConfigurationReader configReader = contextManager.getConfigReader();
+        final PwmApplication pwmApplication = contextManager.getPwmApplication();
+
+        try {
+            // add a random security key
+            storedConfiguration.initNewRandomSecurityKey();
+
+            configReader.saveConfiguration(storedConfiguration, pwmApplication, null);
+
+            contextManager.requestPwmApplicationRestart();
+        } catch (PwmException e) {
+            throw new PwmOperationalException(e.getErrorInformation());
+        } catch (Exception e) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,"unable to save configuration: " + e.getLocalizedMessage());
+            throw new PwmOperationalException(errorInformation);
+        }
+    }
+
+    public static SchemaOperationResult extendSchema(final ConfigGuideBean configGuideBean, final boolean doSchemaExtension) {
+        final Map<ConfigGuideFormField,String> form = configGuideBean.getFormData();
+        final boolean ldapServerSecure = "true".equalsIgnoreCase(form.get(ConfigGuideFormField.PARAM_LDAP_SECURE));
+        final String ldapUrl = "ldap" + (ldapServerSecure ? "s" : "") + "://" + form.get(ConfigGuideFormField.PARAM_LDAP_HOST) + ":" + form.get(ConfigGuideFormField.PARAM_LDAP_PORT);
+        try {
+            final ChaiConfiguration chaiConfiguration = new ChaiConfiguration(ldapUrl, form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_DN), form.get(ConfigGuideFormField.PARAM_LDAP_PROXY_PW));
+            chaiConfiguration.setSetting(ChaiSetting.PROMISCUOUS_SSL,"true");
+            final ChaiProvider chaiProvider = ChaiProviderFactory.createProvider(chaiConfiguration);
+            if (doSchemaExtension) {
+                return SchemaManager.extendSchema(chaiProvider);
+            } else {
+                return SchemaManager.checkExistingSchema(chaiProvider);
+            }
+        } catch (Exception e) {
+            LOGGER.error("unable to create schema extender object: " + e.getMessage());
+            return null;
+        }
+    }
+
+    static void forwardToJSP(
+            final PwmRequest pwmRequest
+    )
+            throws IOException, ServletException, PwmUnrecoverableException
+    {
+        final ConfigGuideBean configGuideBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest,ConfigGuideBean.class);
+
+        if (configGuideBean.getStep() == GuideStep.LDAP_PERMISSIONS) {
+            final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator(ConfigGuideForm.generateStoredConfig(configGuideBean));
+            pwmRequest.setAttribute(PwmRequestAttribute.LdapPermissionItems, ldapPermissionCalculator);
+        }
+
+        final HttpServletRequest req = pwmRequest.getHttpServletRequest();
+        final ServletContext servletContext = req.getSession().getServletContext();
+        String destURL = '/' + PwmConstants.URL_JSP_CONFIG_GUIDE;
+        destURL = destURL.replace("%1%", configGuideBean.getStep().toString().toLowerCase());
+        servletContext.getRequestDispatcher(destURL).forward(req, pwmRequest.getPwmResponse().getHttpServletResponse());
+    }
+
+    public static Percent stepProgress(final GuideStep step) {
+        final int ordinal = step.ordinal();
+        final int total = GuideStep.values().length - 2;
+        return new Percent(ordinal,total);
+    }
+
+    static void checkLdapServer(final ConfigGuideBean configGuideBean)
+            throws PwmOperationalException, IOException, PwmUnrecoverableException {
+        final Map<ConfigGuideFormField,String> formData = configGuideBean.getFormData();
+        final String host = formData.get(ConfigGuideFormField.PARAM_LDAP_HOST);
+        final int port = Integer.parseInt(formData.get(ConfigGuideFormField.PARAM_LDAP_PORT));
+
+        { // socket test
+            final InetAddress inetAddress = InetAddress.getByName(host);
+            final SocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
+            final Socket socket = new Socket();
+
+            final int timeout = 2000;
+            socket.connect(socketAddress, timeout);
+        }
+
+        if (Boolean.parseBoolean(formData.get(ConfigGuideFormField.PARAM_LDAP_SECURE))) {
+            X509Utils.readRemoteCertificates(host, port);
+        }
+    }
+
+
+    public static void restUploadConfig(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException, IOException, ServletException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        final HttpServletRequest req = pwmRequest.getHttpServletRequest();
+
+        if (pwmApplication.getApplicationMode() == PwmApplicationMode.RUNNING) {
+            final String errorMsg = "config upload is not permitted when in running mode";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_UPLOAD_FAILURE,errorMsg,new String[]{errorMsg});
+            pwmRequest.respondWithError(errorInformation, true);
+        }
+
+        if (ServletFileUpload.isMultipartContent(req)) {
+            final InputStream uploadedFile = pwmRequest.readFileUploadStream(PwmConstants.PARAM_FILE_UPLOAD);
+            if (uploadedFile != null) {
+                try {
+                    final StoredConfigurationImpl storedConfig = StoredConfigurationImpl.fromXml(uploadedFile);
+                    final List<String> configErrors = storedConfig.validateValues();
+                    if (configErrors != null && !configErrors.isEmpty()) {
+                        throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,configErrors.get(0)));
+                    }
+                    ConfigGuideUtils.writeConfig(ContextManager.getContextManager(req.getSession()),storedConfig);
+                    LOGGER.trace(pwmSession, "read config from file: " + storedConfig.toString());
+                    final RestResultBean restResultBean = RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown);
+                    pwmRequest.getPwmResponse().outputJsonResult(restResultBean);
+                    req.getSession().invalidate();
+                } catch (PwmException e) {
+                    final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
+                    pwmRequest.getPwmResponse().outputJsonResult(restResultBean);
+                    LOGGER.error(pwmSession, e.getErrorInformation().toDebugStr());
+                }
+            } else {
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_UPLOAD_FAILURE, "error reading config file: no file present in upload");
+                final RestResultBean restResultBean = RestResultBean.fromError(errorInformation, pwmRequest);
+                pwmRequest.getPwmResponse().outputJsonResult(restResultBean);
+                LOGGER.error(pwmSession, errorInformation.toDebugStr());
+            }
+        }
+    }
+
+}

+ 26 - 1
server/src/main/java/password/pwm/http/servlet/configguide/GuideStep.java

@@ -28,6 +28,7 @@ import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.ConfigGuideBean;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Set;
@@ -53,6 +54,9 @@ public enum GuideStep {
     END(null),
     FINISH(null),
 
+    NEXT(NeverVisible.class),
+    PREVIOUS(NeverVisible.class),
+
     ;
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(GuideStep.class);
@@ -72,10 +76,24 @@ public enum GuideStep {
     }
 
     private GuideStep peer(final int distance) {
-        return values()[(this.ordinal()+distance) % values().length];
+        if (distance != -1 && distance != 1) {
+            throw new IllegalArgumentException("distance must be +1 or -1");
+        }
+
+        final int nextOrdinal = JavaHelper.rangeCheck(
+                START.ordinal(),
+                FINISH.ordinal(),
+                this.ordinal() + distance
+        );
+
+        return GuideStep.values()[nextOrdinal];
     }
 
     boolean visible(final ConfigGuideBean configGuideBean) {
+        if (this == NEXT || this == PREVIOUS) {
+            return false;
+        }
+
         if (visibilityCheckClass != null) {
             final VisibilityCheck visibilityCheckImpl;
             try {
@@ -85,6 +103,7 @@ public enum GuideStep {
                 LOGGER.error("unexpected error during step visibility check: " + e.getMessage(), e);
             }
         }
+
         return true;
     }
 
@@ -126,4 +145,10 @@ public enum GuideStep {
                     !PwmSettingCategory.TELEMETRY.isHidden();
         }
     }
+
+    static class NeverVisible implements VisibilityCheck {
+        public boolean visible(final ConfigGuideBean configGuideBean) {
+            return false;
+        }
+    }
 }

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -48,7 +48,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ConfigManagerBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
-import password.pwm.http.servlet.configguide.ConfigGuideServlet;
+import password.pwm.http.servlet.configguide.ConfigGuideUtils;
 import password.pwm.i18n.Admin;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.Display;
@@ -143,7 +143,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
                     break;
 
                 case uploadConfig:
-                    ConfigGuideServlet.restUploadConfig(pwmRequest);
+                    ConfigGuideUtils.restUploadConfig(pwmRequest);
                     return;
 
                 case summary:

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

@@ -585,7 +585,7 @@ Setting_Description_pwm.publishStats.enable=Enable this option to periodically s
 Setting_Description_pwm.publishStats.siteDescription=This optional value can be included if you want to identify your site when the anonymous statistics are published.   You could use your organizations name or other descriptive value.
 Setting_Description_pwm.securityKey=<p>Specify a Security Key used for cryptographic functions such as the token verification. @PwmAppName@ requires a value if you enabled tokens for any of modules and configured a token storage method. @PwmAppName@ uses this value similar to how a cryptographic security certificate uses the private key.</p> <p>If configured, this value must be at least 32 characters in length.  The longer and more random this value, the more secure its uses are.  If multiple instances are in use, you must configure each instance with the same value.</p><p>Upon initial setup, @PwmAppName@ assigns a random security key to this value that you can change at any time, however, any outstanding tokens or other material generated by an old security key become invalid.</p>
 Setting_Description_pwm.seedlist.location=Specify the location of the seed list in the form of a valid URL. When @PwmAppName@ randomly generates passwords, it can generate a "friendly", random password suggestions to users.  It does this by using a "seed" word or words, and then modifying that word randomly until it is sufficiently complex and meets the configured rules computed for the user.<br/><br/>The value must be a valid URL, using the protocol "file" (local file system), "http", or "https".
-Setting_Description_pwm.selfURL=<p>The URL to this application, as seen by users. @PwmAppName@ uses the value in email macros and other user-facing communications.</p><p>The URL must use a valid fully qualified hostname. Do not use a network address.</p><p>In simple environments, the URL will be the base of the URL in the browser you are currently using to view this page, however in more complex environments the URL will typically be an upstream proxy, gateway or network device.</p>
+Setting_Description_pwm.selfURL=<p>The URL to this application, as seen by users. @PwmAppName@ uses the value in email macros and other user-facing communications.</p><p>The URL must use a valid fully qualified hostname. Do not use a network address.</p><p>In simple environments, the URL will be the base of the URL in the browser you are currently using to view this page, however in more complex environments the URL will typically be an upstream proxy, gateway or network device.</p><p>The URL should include the path to the base application, typically <code>/@Case:lower:[[@PwmAppName@]]@</code>.</p>
 Setting_Description_pwm.wordlist.location=Specify a word list file URL for dictionary checking to prevent users from using commonly used words as passwords.   Using word lists is an important part of password security.  Word lists are used by intruders to guess common passwords.   The default word list included contains commonly used English passwords.  <br/><br/>The first time a startup occurs with a new word list setting, it takes some time to compile the word list into a database.  See the status screen and logs for progress information.  The word list file format is one or more text files containing a single word per line, enclosed in a ZIP file.  The String <i>\!\#comment\:</i> at the beginning of a line indicates a comment. <br/><br/>The value must be a valid URL, using the protocol "file" (local file system), "http", or "https".
 Setting_Description_recovery.action=Add actions to take when the user completes the forgotten password process.
 Setting_Description_recovery.allowWhenLocked=Enable this option to allow users to use the forgotten password feature when the account is intruder locked in LDAP.  This feature is not available when a user is using NMAS stored responses.

+ 16 - 0
server/src/main/webapp/WEB-INF/jsp/configguide-app.jsp

@@ -1,6 +1,7 @@
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
+<%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -81,8 +82,23 @@
             PWM_MAIN.addEventHandler('configForm','input',function(){handleFormActivity()});
 
             checkIfNextEnabled();
+
+            populateSiteUrl();
         });
 
+        function populateSiteUrl() {
+            var siteUrlInput = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_APP_SITEURL%>');
+            if (siteUrlInput.value.length > 0) {
+                return;
+            }
+
+            var suggestedSiteUrl = window.location.protocol + '//' + window.location.host + PWM_GLOBAL['url-context'];
+            siteUrlInput.value = suggestedSiteUrl;
+
+            handleFormActivity();
+            checkIfNextEnabled();
+        }
+
         function checkIfNextEnabled() {
             var siteUrlInput = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_APP_SITEURL%>');
             var passed = siteUrlInput.value.length > 1 && new RegExp(siteUrlInput.getAttribute('pattern')).test(siteUrlInput.value);

+ 4 - 0
server/src/main/webapp/WEB-INF/jsp/configguide-end.jsp

@@ -39,6 +39,10 @@
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
         <p>The installation process is now complete.  You can go back to any previous step if you would like to make changes, or click
             <i>Save Configuration</i> to save the configuration and restart the application.</p>
+            <p>To facilitate further configuration troubleshooting, the setting
+                <code><%=PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS.toMenuLocationDebug(null, JspUtility.locale(request))%></code> will be
+                enabled by default.  It should be disabled before the server is used in a production environment.
+            </p>
         <br/>
         <div id="outline_ldap-server" class="setting_outline">
             <div id="titlePaneHeader-ldap-server" class="setting_title">Configuration Summary</div>

+ 2 - 1
server/src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp

@@ -7,6 +7,7 @@
 <%@ page import="password.pwm.ldap.schema.SchemaDefinition" %>
 <%@ page import="java.util.List" %>
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideUtils" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -40,7 +41,7 @@
     boolean existingSchemaGood = false;
     String schemaActivityLog = "";
     try {
-        final SchemaOperationResult schemaManager = ConfigGuideServlet.extendSchema(configGuideBean,false);
+        final SchemaOperationResult schemaManager = ConfigGuideUtils.extendSchema(configGuideBean,false);
         existingSchemaGood = schemaManager.isSuccess();
         schemaActivityLog = schemaManager.getOperationLog();
     } catch (Exception e) {

+ 43 - 6
server/src/main/webapp/WEB-INF/jsp/configguide-ldap_testuser.jsp

@@ -36,7 +36,7 @@
 <div id="wrapper">
     <%@ include file="fragment/configguide-header.jsp"%>
     <div id="centerbody">
-        <form id="configForm">
+        <form id="configForm" name="configForm">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <br/>
             <div id="outline_ldap" class="setting_outline">
@@ -46,7 +46,12 @@
                 <div class="setting_body">
                     <pwm:display key="ldap_testuser_description" bundle="ConfigGuide"/>
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>" style="padding-left: 5px; padding-top: 5px">
+                        <label class="checkboxWrapper"><input type="radio" id="<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>-enabled" name="<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>" value="true">Enabled</label>
+                        <br/>
+                        <label class="checkboxWrapper"><input type="radio" id="<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>-disabled" name="<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>" value="false">Disabled</label>
+                        <br/>
+                        <br/>
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>" style="padding-left: 5px; padding-top: 5px" style="display: none">
                             Example: <code><%=PwmSetting.LDAP_TEST_USER_DN.getExample(ConfigGuideForm.generateStoredConfig(configGuideBean).getTemplateSet())%></code>
                             <br/><br/>
                             <b>LDAP Test User DN</b>
@@ -80,6 +85,12 @@
         PWM_GUIDE.updateForm();
         clearHealthDiv();
         checkIfNextEnabled();
+
+        if (PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>-enabled').checked) {
+            PWM_MAIN.getObject('titlePane_<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>').style.display = 'inline';
+        } else {
+            PWM_MAIN.getObject('titlePane_<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>').style.display = 'none';
+        }
     }
 
     function clearHealthDiv() {
@@ -89,11 +100,16 @@
     PWM_GLOBAL['startupFunctions'].push(function(){
         PWM_VAR['originalHealthBody'] = PWM_MAIN.getObject('healthBody').innerHTML;
         checkIfNextEnabled();
+        initEnabledField();
+
+        PWM_MAIN.addEventHandler('configForm','input,click',function(){
+            handleFormActivity();
+        });
+
 
         PWM_MAIN.addEventHandler('button_next','click',function(){PWM_GUIDE.gotoStep('NEXT')});
         PWM_MAIN.addEventHandler('button_previous','click',function(){PWM_GUIDE.gotoStep('PREVIOUS')});
 
-        PWM_MAIN.addEventHandler('configForm','input',function(){handleFormActivity()});
         PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()});
 
         PWM_MAIN.addEventHandler('button-browse-testUser','click',function(){
@@ -106,14 +122,35 @@
 
     function checkIfNextEnabled() {
         var fieldValue = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>').value;
-        PWM_MAIN.getObject('button_next').disabled = false;
-        if (fieldValue.length && fieldValue.length > 0) {
+        if (PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>-enabled').checked) {
+            var goodHealth = false;
+            var hasValue = true;
+
             if (PWM_GLOBAL['pwm-health'] !== 'GOOD' && PWM_GLOBAL['pwm-health'] !== 'CONFIG') {
-                PWM_MAIN.getObject('button_next').disabled = true;
+                goodHealth = true;
             }
+
+            if (fieldValue.length > 0) {
+                hasValue = true;
+            }
+
+            PWM_MAIN.getObject('button_next').disabled = (goodHealth && hasValue)
+        } else {
+            PWM_MAIN.getObject('button_next').disabled = false;
         }
     }
 
+    function initEnabledField() {
+        <% final String currentValue = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED);%>
+        <% if ("true".equals(currentValue)) { %>
+        PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>-enabled').checked = true;
+        <% } else if ("false".equals(currentValue)) { %>
+        PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER_ENABLED%>-disabled').checked = true;
+        <% } %>
+
+        handleFormActivity();
+    }
+
     function loadHealth() {
         var options = {};
         options['sourceUrl'] = 'ConfigGuide?processAction=ldapHealth';

+ 2 - 1
server/src/main/webapp/WEB-INF/jsp/fragment/configguide-header.jsp

@@ -1,6 +1,7 @@
 <%@ page import="password.pwm.http.bean.ConfigGuideBean" %>
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideServlet" %>
 <%@ page import="password.pwm.http.JspUtility" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideUtils" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -24,7 +25,7 @@
   --%>
 
 <% final ConfigGuideBean headerCgb = JspUtility.getSessionBean(pageContext, ConfigGuideBean.class);%>
-<% final float pctComplete = ConfigGuideServlet.stepProgress(headerCgb.getStep()).asFloat(); %>
+<% final float pctComplete = ConfigGuideUtils.stepProgress(headerCgb.getStep()).asFloat(); %>
 <div id="header" class="configguide-header">
     <div id="header-company-logo">
     </div>