Forráskód Böngészése

Merge remote-tracking branch 'pwm/master' into cas-config

Ian Wright 8 éve
szülő
commit
bd28910e7d
70 módosított fájl, 1524 hozzáadás és 578 törlés
  1. 0 6
      checkstyle.xml
  2. 3 0
      import-control.xml
  3. 5 4
      pom.xml
  4. 6 1
      src/main/java/password/pwm/AppProperty.java
  5. 3 1
      src/main/java/password/pwm/PwmApplication.java
  6. 3 0
      src/main/java/password/pwm/PwmConstants.java
  7. 1 0
      src/main/java/password/pwm/bean/SessionLabel.java
  8. 19 10
      src/main/java/password/pwm/bean/TelemetryPublishBean.java
  9. 31 9
      src/main/java/password/pwm/config/FormUtility.java
  10. 4 0
      src/main/java/password/pwm/config/PwmSetting.java
  11. 1 0
      src/main/java/password/pwm/error/PwmError.java
  12. 9 3
      src/main/java/password/pwm/health/LDAPStatusChecker.java
  13. 1 0
      src/main/java/password/pwm/http/PwmRequestAttribute.java
  14. 7 2
      src/main/java/password/pwm/http/PwmSessionWrapper.java
  15. 6 49
      src/main/java/password/pwm/http/bean/ConfigGuideBean.java
  16. 2 1
      src/main/java/password/pwm/http/bean/NewUserBean.java
  17. 1 2
      src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  18. 7 1
      src/main/java/password/pwm/http/servlet/UpdateProfileServlet.java
  19. 33 62
      src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java
  20. 65 0
      src/main/java/password/pwm/http/servlet/configguide/ConfigGuideFormField.java
  21. 15 14
      src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  22. 19 1
      src/main/java/password/pwm/http/servlet/configguide/GuideStep.java
  23. 16 5
      src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java
  24. 62 13
      src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  25. 12 0
      src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  26. 6 24
      src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartDataBean.java
  27. 5 24
      src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartReferenceBean.java
  28. 26 40
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java
  29. 19 8
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  30. 2 2
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  31. 1 1
      src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  32. 1 1
      src/main/java/password/pwm/ldap/search/UserSearchEngine.java
  33. 87 0
      src/main/java/password/pwm/svc/PwmServiceEnum.java
  34. 2 79
      src/main/java/password/pwm/svc/PwmServiceManager.java
  35. 11 106
      src/main/java/password/pwm/svc/stats/StatisticsManager.java
  36. 207 0
      src/main/java/password/pwm/svc/telemetry/FtpTelemetrySender.java
  37. 86 0
      src/main/java/password/pwm/svc/telemetry/HttpTelemetrySender.java
  38. 33 0
      src/main/java/password/pwm/svc/telemetry/TelemetrySender.java
  39. 338 0
      src/main/java/password/pwm/svc/telemetry/TelemetryService.java
  40. 3 2
      src/main/java/password/pwm/svc/telemetry/VersionChecker.java
  41. 1 1
      src/main/java/password/pwm/svc/token/TokenService.java
  42. 12 0
      src/main/java/password/pwm/util/java/JavaHelper.java
  43. 6 0
      src/main/java/password/pwm/util/java/TimeDuration.java
  44. 4 0
      src/main/resources/password/pwm/AppProperty.properties
  45. 12 5
      src/main/resources/password/pwm/config/PwmSetting.xml
  46. 2 0
      src/main/resources/password/pwm/i18n/ConfigGuide.properties
  47. 4 2
      src/main/resources/password/pwm/i18n/PwmSetting.properties
  48. 5 4
      src/main/webapp/WEB-INF/jsp/configguide-app.jsp
  49. 13 12
      src/main/webapp/WEB-INF/jsp/configguide-database.jsp
  50. 11 10
      src/main/webapp/WEB-INF/jsp/configguide-end.jsp
  51. 97 0
      src/main/webapp/WEB-INF/jsp/configguide-eula.jsp
  52. 3 2
      src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp
  53. 4 3
      src/main/webapp/WEB-INF/jsp/configguide-ldap_cert.jsp
  54. 4 3
      src/main/webapp/WEB-INF/jsp/configguide-ldap_context.jsp
  55. 4 3
      src/main/webapp/WEB-INF/jsp/configguide-ldap_proxy.jsp
  56. 3 2
      src/main/webapp/WEB-INF/jsp/configguide-ldap_schema.jsp
  57. 14 13
      src/main/webapp/WEB-INF/jsp/configguide-ldap_server.jsp
  58. 5 4
      src/main/webapp/WEB-INF/jsp/configguide-ldap_testuser.jsp
  59. 5 4
      src/main/webapp/WEB-INF/jsp/configguide-password.jsp
  60. 0 18
      src/main/webapp/WEB-INF/jsp/configguide-start.jsp
  61. 6 5
      src/main/webapp/WEB-INF/jsp/configguide-storage.jsp
  62. 120 0
      src/main/webapp/WEB-INF/jsp/configguide-telemetry.jsp
  63. 6 5
      src/main/webapp/WEB-INF/jsp/configguide-template.jsp
  64. 1 0
      src/main/webapp/WEB-INF/jsp/error-http.jsp
  65. 1 1
      src/main/webapp/WEB-INF/jsp/newuser-entercode.jsp
  66. 4 4
      src/main/webapp/WEB-INF/jsp/newuser-profilechoice.jsp
  67. 0 5
      src/main/webapp/WEB-INF/web.xml
  68. 10 0
      src/main/webapp/public/resources/style.css
  69. 9 1
      src/main/webapp/public/resources/text/eula.txt
  70. 0 0
      src/main/webapp/public/resources/text/privacy.txt

+ 0 - 6
checkstyle.xml

@@ -228,11 +228,6 @@
         <!--
         <module name="AvoidNestedBlocks"/>
         -->
-        <module name="EmptyBlock">
-            <property name="option" value="TEXT"/>
-            <property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
-        </module>
-
 
 
         <!-- Checks for common coding problems               -->
@@ -302,7 +297,6 @@
         <module name="EqualsAvoidNull"/>
 
         <module name="MutableException"/>
-        <module name="OuterTypeFilename"/>
         <module name="TodoComment"/>
         <module name="NoLineWrap"/>
         <module name="OneTopLevelClass"/>

+ 3 - 0
import-control.xml

@@ -40,6 +40,9 @@
     <!-- chai -->
     <allow pkg="com.novell.ldapchai"/>
 
+    <!-- commons ftp client -->
+    <allow pkg="org.apache.commons.net.ftp"/>
+
     <!-- xml  -->
     <allow pkg="org.jdom2"/>
     <allow pkg="javax.xml"/>

+ 5 - 4
pom.xml

@@ -2,10 +2,6 @@
 
     <modelVersion>4.0.0</modelVersion>
 
-    <prerequisites>
-        <maven>3.2</maven>
-    </prerequisites>
-
     <groupId>org.pwm-project</groupId>
     <artifactId>pwm</artifactId>
     <version>1.8.0-SNAPSHOT</version>
@@ -616,6 +612,11 @@
             <artifactId>ldapchai</artifactId>
             <version>0.6.9</version>
         </dependency>
+        <dependency>
+            <groupId>commons-net</groupId>
+            <artifactId>commons-net</artifactId>
+            <version>3.6</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-csv</artifactId>

+ 6 - 1
src/main/java/password/pwm/AppProperty.java

@@ -29,7 +29,7 @@ import java.util.ResourceBundle;
  * by an associated {@code AppProperty.properties} file.  Properties can be overridden by the application administrator in
  * the configuration using the setting {@link password.pwm.config.PwmSetting#APP_PROPERTY_OVERRIDES}.
  */
-public enum     AppProperty {
+public enum AppProperty {
 
     APPLICATION_FILELOCK_FILENAME                   ("application.fileLock.filename"),
     APPLICATION_FILELOCK_WAIT_SECONDS               ("application.fileLock.waitSeconds"),
@@ -287,6 +287,11 @@ public enum     AppProperty {
     TOKEN_RESEND_DELAY_MS                           ("token.resend.delayMS"),
     TOKEN_REMOVE_ON_CLAIM                           ("token.removeOnClaim"),
     TOKEN_VERIFY_PW_MODIFY_TIME                     ("token.verifyPwModifyTime"),
+    TELEMETRY_SENDER_IMPLEMENTATION                 ("telemetry.senderImplementation"),
+    TELEMETRY_SENDER_SETTINGS                       ("telemetry.senderSettings"),
+    TELEMETRY_SEND_FREQUENCY_SECONDS                ("telemetry.sendFrequencySeconds"),
+    TELEMETRY_MIN_AUTHENTICATIONS                   ("telemetry.minimumAuthentications"),
+
 
 
     /** Regular expression to be used for matching URLs to be shortened by the URL Shortening Service Class. */

+ 3 - 1
src/main/java/password/pwm/PwmApplication.java

@@ -59,7 +59,7 @@ import password.pwm.svc.wordlist.SeedlistManager;
 import password.pwm.svc.wordlist.SharedHistoryManager;
 import password.pwm.svc.wordlist.WordlistManager;
 import password.pwm.util.PasswordData;
-import password.pwm.util.VersionChecker;
+import password.pwm.svc.telemetry.VersionChecker;
 import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
 import password.pwm.util.db.DatabaseAccessor;
 import password.pwm.util.db.DatabaseService;
@@ -127,6 +127,8 @@ public class PwmApplication {
         CONFIG_LOGIN_HISTORY("config.loginHistory"),
         LOCALDB_LOGGER_STORAGE_FORMAT("localdb.logger.storage.format"),
 
+        TELEMETRY_LAST_PUBLISH_TIMESTAMP("telemetry.lastPublish.timestamp")
+
         ;
 
         private final String key;

+ 3 - 0
src/main/java/password/pwm/PwmConstants.java

@@ -170,6 +170,9 @@ public abstract class PwmConstants {
 
     public static final String VALUE_REPLACEMENT_USERNAME = "%USERNAME%";
 
+    public static final String RESOURCE_FILE_EULA_TXT = "eula.txt";
+    public static final String RESOURCE_FILE_PRIVACY_TXT = "privacy.txt";
+
     // don't worry.  look over there.
     public static final String[] X_AMB_HEADER = new String[]{
             "bonjour!",

+ 1 - 0
src/main/java/password/pwm/bean/SessionLabel.java

@@ -38,6 +38,7 @@ public class SessionLabel implements Serializable {
     public static final SessionLabel HEALTH_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"health",null,null);
     public static final SessionLabel REPORTING_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"reporting",null,null);
     public static final SessionLabel AUDITING_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"auditing",null,null);
+    public static final SessionLabel TELEMETRY_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"telemetry",null,null);
 
     private final String sessionID;
     private final UserIdentity userIdentity;

+ 19 - 10
src/main/java/password/pwm/bean/StatsPublishBean.java → src/main/java/password/pwm/bean/TelemetryPublishBean.java

@@ -22,7 +22,8 @@
 
 package password.pwm.bean;
 
-import lombok.AllArgsConstructor;
+import com.novell.ldapchai.provider.ChaiProvider;
+import lombok.Builder;
 import lombok.Getter;
 
 import java.io.Serializable;
@@ -31,20 +32,28 @@ import java.util.List;
 import java.util.Map;
 
 @Getter
-@AllArgsConstructor
-public class StatsPublishBean implements Serializable {
+@Builder
+public class TelemetryPublishBean implements Serializable {
+    private final String id;
     private final String instanceID;
+    private final String siteDescription;
+    private final Instant installTime;
     private final Instant timestamp;
-    private final Map<String,String> totalStatistics;
+    private final List<ChaiProvider.DIRECTORY_VENDOR> ldapVendor;
+    private final Map<String,String> statistics;
     private final List<String> configuredSettings;
     private final String versionBuild;
     private final String versionVersion;
-    private final Map<String,String> otherInfo;
+    private final Environment environment;
 
-    public enum KEYS {
-        SITE_URL,
-        SITE_DESCRIPTION,
-        INSTALL_DATE,
-        LDAP_VENDOR
+    @Getter
+    @Builder
+    public static class Environment implements Serializable {
+        String osName;
+        String osVersion;
+        String javaVendor;
+        String javaName;
+        String javaVersion;
+        boolean appliance;
     }
 }

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

@@ -144,7 +144,7 @@ public class FormUtility {
     }
 
     public static Map<String,String> asStringMap(final Map<FormConfiguration, String> input) {
-        final Map<String,String> returnObj = new HashMap<>();
+        final Map<String,String> returnObj = new LinkedHashMap<>();
         for (final FormConfiguration formConfiguration : input.keySet()) {
             returnObj.put(formConfiguration.getName(), input.get(formConfiguration));
             if (formConfiguration.isConfirmationRequired()) {
@@ -156,6 +156,17 @@ public class FormUtility {
         return returnObj;
     }
 
+    public static Map<FormConfiguration,String> asFormConfigurationMap(final List<FormConfiguration> formConfigurations, final Map<String, String> values) {
+        final Map<FormConfiguration, String> returnMap = new LinkedHashMap<>();
+        for (final FormConfiguration formConfiguration : formConfigurations) {
+            final String name = formConfiguration.getName();
+            final String value = values.get(name);
+            returnMap.put(formConfiguration, value);
+        }
+        return returnMap;
+    }
+
+
     public static Map<FormConfiguration, String> readFormValuesFromRequest(
             final PwmRequest pwmRequest,
             final Collection<FormConfiguration> formItems,
@@ -167,24 +178,35 @@ public class FormUtility {
         return readFormValuesFromMap(tempMap, formItems, locale);
     }
 
+    public enum ValidationFlag {
+        allowResultCaching,
+        checkReadOnlyAndHidden,
+    }
+
     public static void validateFormValueUniqueness(
             final PwmApplication pwmApplication,
             final Map<FormConfiguration, String> formValues,
             final Locale locale,
             final Collection<UserIdentity> excludeDN,
-            final boolean allowResultCaching
+            final ValidationFlag... validationFlags
     )
             throws PwmDataValidationException, PwmUnrecoverableException
     {
+        final boolean allowResultCaching = JavaHelper.enumArrayContainsValue(validationFlags, ValidationFlag.allowResultCaching);
+        final boolean checkReadOnlyAndHidden = JavaHelper.enumArrayContainsValue(validationFlags, ValidationFlag.checkReadOnlyAndHidden);
+
+
         final Map<String, String> filterClauses = new HashMap<>();
         final Map<String,String> labelMap = new HashMap<>();
         for (final FormConfiguration formItem : formValues.keySet()) {
-            if (formItem.isUnique() && !formItem.isReadonly()) {
-                if (formItem.getType() != FormConfiguration.Type.hidden) {
-                    final String value = formValues.get(formItem);
-                    if (value != null && value.length() > 0) {
-                        filterClauses.put(formItem.getName(), value);
-                        labelMap.put(formItem.getName(), formItem.getLabel(locale));
+            if (formItem.isUnique()) {
+                if (checkReadOnlyAndHidden || formItem.isReadonly()) {
+                    if (checkReadOnlyAndHidden || (formItem.getType() != FormConfiguration.Type.hidden)) {
+                        final String value = formValues.get(formItem);
+                        if (value != null && value.length() > 0) {
+                            filterClauses.put(formItem.getName(), value);
+                            labelMap.put(formItem.getName(), formItem.getLabel(locale));
+                        }
                     }
                 }
             }
@@ -250,7 +272,7 @@ public class FormUtility {
                     resultSearchSizeLimit,
                     Collections.emptyList(),
                     SessionLabel.SYSTEM_LABEL
-                    ));
+            ));
 
             if (excludeDN != null && !excludeDN.isEmpty()) {
                 for (final UserIdentity loopIgnoreIdentity : excludeDN) {

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

@@ -796,6 +796,8 @@ public enum PwmSetting {
             "newUser.minimumWaitTime", PwmSettingSyntax.DURATION, PwmSettingCategory.NEWUSER_PROFILE),
     NEWUSER_PROFILE_DISPLAY_NAME(
             "newUser.profile.displayName", PwmSettingSyntax.LOCALIZED_STRING, PwmSettingCategory.NEWUSER_PROFILE),
+    NEWUSER_PROFILE_DISPLAY_VISIBLE(
+            "newUser.profile.visible", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_PROFILE),
     NEWUSER_REDIRECT_URL(
             "newUser.redirectUrl", PwmSettingSyntax.STRING, PwmSettingCategory.NEWUSER_PROFILE),
     NEWUSER_PROMPT_FOR_PASSWORD(
@@ -915,6 +917,8 @@ public enum PwmSetting {
             "peopleSearch.orgChart.parentAttribute", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH),
     PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE(
             "peopleSearch.orgChart.childAttribute", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH),
+    PEOPLE_SEARCH_ORGCHART_ASSISTANT_ATTRIBUTE(
+            "peopleSearch.orgChart.assistantAttribute", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH),
 
 
 

+ 1 - 0
src/main/java/password/pwm/error/PwmError.java

@@ -165,6 +165,7 @@ public enum PwmError {
     ERROR_PASSWORD_ONLY_BAD(        5089, "Error_PasswordOnlyBad",          null),
 
     ERROR_REMOTE_ERROR_VALUE(       6000, "Error_RemoteErrorValue",         null, ErrorFlag.Permanent),
+    ERROR_TELEMETRY_SEND_ERROR(     6001, "Error_TelemetrySendError",       null),
 
     ERROR_FIELD_REQUIRED(           5100, "Error_FieldRequired",            null),
     ERROR_FIELD_NOT_A_NUMBER(       5101, "Error_FieldNotANumber",          null),

+ 9 - 3
src/main/java/password/pwm/health/LDAPStatusChecker.java

@@ -59,6 +59,7 @@ import password.pwm.ldap.UserInfoFactory;
 import password.pwm.util.PasswordData;
 import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.operations.PasswordUtility;
@@ -130,11 +131,16 @@ public class LDAPStatusChecker implements HealthChecker {
             }
         }
 
-        returnRecords.addAll(checkVendorSameness(pwmApplication));
+        if (config.getLdapProfiles() != null && !config.getLdapProfiles().isEmpty()) {
+            final List<String> urls = config.getLdapProfiles().values().iterator().next().readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS);
+            if (urls != null && !urls.isEmpty() && !StringUtil.isEmpty(urls.iterator().next())) {
+                returnRecords.addAll(checkVendorSameness(pwmApplication));
 
-        returnRecords.addAll(checkUserPermissionValues(pwmApplication));
+                returnRecords.addAll(checkUserPermissionValues(pwmApplication));
 
-        returnRecords.addAll(checkLdapDNSyntaxValues(pwmApplication));
+                returnRecords.addAll(checkLdapDNSyntaxValues(pwmApplication));
+            }
+        }
 
         return returnRecords;
     }

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

@@ -80,6 +80,7 @@ public enum PwmRequestAttribute {
     GuestMaximumValidDays,
 
     NewUser_FormShowBackButton,
+    NewUser_VisibleProfiles,
 
     CookieBeanStorage,
 

+ 7 - 2
src/main/java/password/pwm/http/PwmSessionWrapper.java

@@ -64,7 +64,11 @@ public class PwmSessionWrapper {
         return returnSession;
     }
 
-    public static PwmSession readPwmSession(final HttpServletRequest httpRequest) throws PwmUnrecoverableException {
+    public static PwmSession readPwmSession(
+            final HttpServletRequest httpRequest
+    )
+            throws PwmUnrecoverableException
+    {
         return readPwmSession(httpRequest.getSession());
     }
 
@@ -72,7 +76,8 @@ public class PwmSessionWrapper {
             final PwmApplication pwmApplication,
             final PwmSession pwmSession,
             final HttpSession httpSession
-    ) throws PwmUnrecoverableException
+    )
+            throws PwmUnrecoverableException
     {
         final IdleTimeoutCalculator.MaxIdleTimeoutResult result = IdleTimeoutCalculator.figureMaxSessionTimeout(pwmApplication, pwmSession);
         if (httpSession.getMaxInactiveInterval() != result.getIdleTimeout().getTotalSeconds()) {

+ 6 - 49
src/main/java/password/pwm/http/bean/ConfigGuideBean.java

@@ -22,9 +22,12 @@
 
 package password.pwm.http.bean;
 
+import lombok.Getter;
+import lombok.Setter;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.value.FileValue;
 import password.pwm.http.servlet.configguide.ConfigGuideForm;
+import password.pwm.http.servlet.configguide.ConfigGuideFormField;
 import password.pwm.http.servlet.configguide.GuideStep;
 
 import java.security.cert.X509Certificate;
@@ -33,63 +36,17 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
+@Getter
+@Setter
 public class ConfigGuideBean extends PwmSessionBean {
 
     private GuideStep step = GuideStep.START;
-    private Map<ConfigGuideForm.FormParameter,String> formData = new HashMap<>(ConfigGuideForm.defaultForm());
+    private final Map<ConfigGuideFormField,String> formData = new HashMap<>(ConfigGuideForm.defaultForm());
     private X509Certificate[] ldapCertificates;
     private boolean certsTrustedbyKeystore = false;
     private boolean useConfiguredCerts = false;
     private FileValue databaseDriver = null;
 
-    public GuideStep getStep() {
-        return step;
-    }
-
-    public void setStep(final GuideStep step) {
-        this.step = step;
-    }
-
-    public Map<ConfigGuideForm.FormParameter, String> getFormData() {
-        return formData;
-    }
-
-    public void setFormData(final Map<ConfigGuideForm.FormParameter, String> formData) {
-        this.formData = formData;
-    }
-
-    public X509Certificate[] getLdapCertificates() {
-        return ldapCertificates;
-    }
-
-    public void setLdapCertificates(final X509Certificate[] ldapCertificates) {
-        this.ldapCertificates = ldapCertificates;
-    }
-
-    public boolean isCertsTrustedbyKeystore() {
-        return certsTrustedbyKeystore;
-    }
-
-    public void setCertsTrustedbyKeystore(final boolean certsTrustedbyKeystore) {
-        this.certsTrustedbyKeystore = certsTrustedbyKeystore;
-    }
-
-    public boolean isUseConfiguredCerts() {
-        return useConfiguredCerts;
-    }
-
-    public void setUseConfiguredCerts(final boolean useConfiguredCerts) {
-        this.useConfiguredCerts = useConfiguredCerts;
-    }
-
-    public FileValue getDatabaseDriver() {
-        return databaseDriver;
-    }
-
-    public void setDatabaseDriver(final FileValue databaseDriver) {
-        this.databaseDriver = databaseDriver;
-    }
-
     public Type getType() {
         return Type.PUBLIC;
     }

+ 2 - 1
src/main/java/password/pwm/http/bean/NewUserBean.java

@@ -33,6 +33,7 @@ import password.pwm.http.servlet.newuser.NewUserForm;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -45,7 +46,7 @@ public class NewUserBean extends PwmSessionBean {
     private String profileID;
 
     @SerializedName("f")
-    private NewUserForm newUserForm;
+    private NewUserForm newUserForm = new NewUserForm(new HashMap<>(),null,null);
 
     @SerializedName("r")
     private Map<String,String> remoteInputData;

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

@@ -227,8 +227,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet {
                     pwmApplication,
                     formValues,
                     ssBean.getLocale(),
-                    Collections.singletonList(guestRegistrationBean.getUpdateUserIdentity()),
-                    false
+                    Collections.singletonList(guestRegistrationBean.getUpdateUserIdentity())
             );
 
             final Date expirationDate = readExpirationFromRequest(pwmRequest);

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

@@ -69,6 +69,7 @@ import password.pwm.ws.server.RestResultBean;
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -528,13 +529,18 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
         // see if the values meet form requirements.
         FormUtility.validateFormValues(pwmRequest.getConfig(), formValues, userLocale);
 
+        final List<FormUtility.ValidationFlag> validationFlags = new ArrayList<>();
+        if (allowResultCaching) {
+            validationFlags.add(FormUtility.ValidationFlag.allowResultCaching);
+        }
+
         // check unique fields against ldap
         FormUtility.validateFormValueUniqueness(
                 pwmRequest.getPwmApplication(),
                 formValues,
                 userLocale,
                 Collections.singletonList(pwmRequest.getPwmSession().getUserInfo().getUserIdentity()),
-                allowResultCaching
+                validationFlags.toArray(new FormUtility.ValidationFlag[validationFlags.size()])
         );
     }
 

+ 33 - 62
src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java

@@ -27,6 +27,7 @@ import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.StoredValue;
 import password.pwm.config.UserPermission;
 import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.StringArrayValue;
@@ -48,56 +49,19 @@ public class ConfigGuideForm {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(ConfigGuideForm.class);
 
-    public static Map<FormParameter,String> defaultForm() {
-        final Map<FormParameter,String> defaultLdapForm = new HashMap<>();
-        for (final FormParameter formParameter : FormParameter.values()) {
+    public static Map<ConfigGuideFormField,String> defaultForm() {
+        final Map<ConfigGuideFormField,String> defaultLdapForm = new HashMap<>();
+        for (final ConfigGuideFormField formParameter : ConfigGuideFormField.values()) {
             defaultLdapForm.put(formParameter, "");
         }
 
-        defaultLdapForm.put(FormParameter.PARAM_LDAP_PORT,"636");
-        defaultLdapForm.put(FormParameter.PARAM_LDAP_SECURE,"true");
+        defaultLdapForm.put(ConfigGuideFormField.PARAM_LDAP_PORT,"636");
+        defaultLdapForm.put(ConfigGuideFormField.PARAM_LDAP_SECURE,"true");
 
         return Collections.unmodifiableMap(defaultLdapForm);
     }
 
 
-    public enum FormParameter {
-        PARAM_TEMPLATE_LDAP(PwmSetting.TEMPLATE_LDAP),
-        PARAM_TEMPLATE_STORAGE(PwmSetting.TEMPLATE_STORAGE),
-
-        PARAM_APP_SITEURL(PwmSetting.PWM_SITE_URL),
-
-        PARAM_LDAP_HOST(null),
-        PARAM_LDAP_PORT(null),
-        PARAM_LDAP_SECURE(null),
-        PARAM_LDAP_PROXY_DN(PwmSetting.LDAP_PROXY_USER_DN),
-        PARAM_LDAP_PROXY_PW(PwmSetting.LDAP_PROXY_USER_PASSWORD),
-
-        PARAM_LDAP_CONTEXT(PwmSetting.LDAP_CONTEXTLESS_ROOT),
-        PARAM_LDAP_TEST_USER(PwmSetting.LDAP_TEST_USER_DN),
-        PARAM_LDAP_ADMIN_GROUP(PwmSetting.QUERY_MATCH_PWM_ADMIN),
-
-        PARAM_DB_CLASSNAME(PwmSetting.DATABASE_CLASS),
-        PARAM_DB_CONNECT_URL(PwmSetting.DATABASE_URL),
-        PARAM_DB_USERNAME(PwmSetting.DATABASE_USERNAME),
-        PARAM_DB_PASSWORD(PwmSetting.DATABASE_PASSWORD),
-        PARAM_DB_VENDOR(PwmSetting.DB_VENDOR_TEMPLATE),
-
-        PARAM_CONFIG_PASSWORD(null),
-
-        ;
-
-        private final PwmSetting pwmSetting;
-
-        FormParameter(final PwmSetting pwmSetting) {
-            this.pwmSetting = pwmSetting;
-        }
-
-        public PwmSetting getPwmSetting() {
-            return pwmSetting;
-        }
-    }
-
     public static StoredConfigurationImpl generateStoredConfig(
             final ConfigGuideBean configGuideBean
     )
@@ -105,24 +69,23 @@ public class ConfigGuideForm {
     {
         final String LDAP_PROFILE_NAME = "default";
 
-        final Map<ConfigGuideForm.FormParameter, String> formData = configGuideBean.getFormData();
+        final Map<ConfigGuideFormField, String> formData = configGuideBean.getFormData();
         final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.newStoredConfiguration();
 
         // templates
         storedConfiguration.writeSetting(PwmSetting.TEMPLATE_LDAP, null, new StringValue(
-                PwmSettingTemplate.templateForString(formData.get(FormParameter.PARAM_TEMPLATE_LDAP), PwmSettingTemplate.Type.LDAP_VENDOR).toString()
+                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(FormParameter.PARAM_TEMPLATE_STORAGE), PwmSettingTemplate.Type.STORAGE).toString()
+                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(FormParameter.PARAM_DB_VENDOR), PwmSettingTemplate.Type.DB_VENDOR).toString()
+                PwmSettingTemplate.templateForString(formData.get(ConfigGuideFormField.PARAM_DB_VENDOR), PwmSettingTemplate.Type.DB_VENDOR).toString()
         ), null);
 
         // establish a default ldap profile
         storedConfiguration.writeSetting(PwmSetting.LDAP_PROFILE_LIST, null, new StringArrayValue(Collections.singletonList(LDAP_PROFILE_NAME)), null);
 
-
         {
             final String newLdapURI = figureLdapUrlFromFormConfig(formData);
             final StringArrayValue newValue = new StringArrayValue(Collections.singletonList(newLdapURI));
@@ -135,43 +98,43 @@ public class ConfigGuideForm {
         }
 
         { // proxy/admin account
-            final String ldapAdminDN = formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN);
-            final String ldapAdminPW = formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_PW);
+            final String ldapAdminDN = formData.get(ConfigGuideFormField.PARAM_LDAP_PROXY_DN);
+            final String ldapAdminPW = formData.get(ConfigGuideFormField.PARAM_LDAP_PROXY_PW);
             storedConfiguration.writeSetting(PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_NAME, new StringValue(ldapAdminDN), null);
             final PasswordValue passwordValue = new PasswordValue(PasswordData.forStringValue(ldapAdminPW));
             storedConfiguration.writeSetting(PwmSetting.LDAP_PROXY_USER_PASSWORD, LDAP_PROFILE_NAME, passwordValue, null);
         }
 
-        storedConfiguration.writeSetting(PwmSetting.LDAP_CONTEXTLESS_ROOT, LDAP_PROFILE_NAME, new StringArrayValue(Collections.singletonList(formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_CONTEXT))), null);
+        storedConfiguration.writeSetting(PwmSetting.LDAP_CONTEXTLESS_ROOT, LDAP_PROFILE_NAME, new StringArrayValue(Collections.singletonList(formData.get(ConfigGuideFormField.PARAM_LDAP_CONTEXT))), null);
 
         {
-            final String ldapContext = formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_CONTEXT);
+            final String ldapContext = formData.get(ConfigGuideFormField.PARAM_LDAP_CONTEXT);
             storedConfiguration.writeSetting(PwmSetting.LDAP_CONTEXTLESS_ROOT, LDAP_PROFILE_NAME, new StringArrayValue(Collections.singletonList(ldapContext)), null);
         }
 
         {
-            final String ldapTestUserDN = formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER);
+            final String ldapTestUserDN = formData.get(ConfigGuideFormField.PARAM_LDAP_TEST_USER);
             storedConfiguration.writeSetting(PwmSetting.LDAP_TEST_USER_DN, LDAP_PROFILE_NAME, new StringValue(ldapTestUserDN), null);
         }
 
         {  // set admin query
-            final String groupDN = formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_ADMIN_GROUP);
+            final String groupDN = formData.get(ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP);
             final List<UserPermission> userPermissions = Collections.singletonList(new UserPermission(UserPermission.Type.ldapGroup, null, null, groupDN));
             storedConfiguration.writeSetting(PwmSetting.QUERY_MATCH_PWM_ADMIN, new UserPermissionValue(userPermissions), null);
         }
 
         {  // database
 
-            final String dbClass = formData.get(ConfigGuideForm.FormParameter.PARAM_DB_CLASSNAME);
+            final String dbClass = formData.get(ConfigGuideFormField.PARAM_DB_CLASSNAME);
             storedConfiguration.writeSetting(PwmSetting.DATABASE_CLASS, null, new StringValue(dbClass), null);
 
-            final String dbUrl = formData.get(ConfigGuideForm.FormParameter.PARAM_DB_CONNECT_URL);
+            final String dbUrl = formData.get(ConfigGuideFormField.PARAM_DB_CONNECT_URL);
             storedConfiguration.writeSetting(PwmSetting.DATABASE_URL, null, new StringValue(dbUrl), null);
 
-            final String dbUser = formData.get(ConfigGuideForm.FormParameter.PARAM_DB_USERNAME);
+            final String dbUser = formData.get(ConfigGuideFormField.PARAM_DB_USERNAME);
             storedConfiguration.writeSetting(PwmSetting.DATABASE_USERNAME, null, new StringValue(dbUser), null);
 
-            final String dbPassword = formData.get(ConfigGuideForm.FormParameter.PARAM_DB_PASSWORD);
+            final String dbPassword = formData.get(ConfigGuideFormField.PARAM_DB_PASSWORD);
             final PasswordValue passwordValue = new PasswordValue(PasswordData.forStringValue(dbPassword));
             storedConfiguration.writeSetting(PwmSetting.DATABASE_PASSWORD, null, passwordValue, null);
 
@@ -181,16 +144,24 @@ public class ConfigGuideForm {
             }
         }
 
+        { //telemetry
+            final boolean telemetryEnabled = Boolean.parseBoolean(formData.get(ConfigGuideFormField.PARAM_TELEMETRY_ENABLE));
+            storedConfiguration.writeSetting(PwmSetting.PUBLISH_STATS_ENABLE, null, new BooleanValue(telemetryEnabled), null);
+
+            final String siteDescription = formData.get(ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION);
+            storedConfiguration.writeSetting(PwmSetting.PUBLISH_STATS_SITE_DESCRIPTION, null, new StringValue(siteDescription), null);
+        }
+
         // set site url
-        storedConfiguration.writeSetting(PwmSetting.PWM_SITE_URL, new StringValue(formData.get(ConfigGuideForm.FormParameter.PARAM_APP_SITEURL)), null);
+        storedConfiguration.writeSetting(PwmSetting.PWM_SITE_URL, new StringValue(formData.get(ConfigGuideFormField.PARAM_APP_SITEURL)), null);
 
         return storedConfiguration;
     }
 
-    static String figureLdapUrlFromFormConfig(final Map<ConfigGuideForm.FormParameter, String> ldapForm) {
-        final String ldapServerIP = ldapForm.get(ConfigGuideForm.FormParameter.PARAM_LDAP_HOST);
-        final String ldapServerPort = ldapForm.get(ConfigGuideForm.FormParameter.PARAM_LDAP_PORT);
-        final boolean ldapServerSecure = "true".equalsIgnoreCase(ldapForm.get(ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE));
+    static String figureLdapUrlFromFormConfig(final Map<ConfigGuideFormField, String> ldapForm) {
+        final String ldapServerIP = ldapForm.get(ConfigGuideFormField.PARAM_LDAP_HOST);
+        final String ldapServerPort = ldapForm.get(ConfigGuideFormField.PARAM_LDAP_PORT);
+        final boolean ldapServerSecure = "true".equalsIgnoreCase(ldapForm.get(ConfigGuideFormField.PARAM_LDAP_SECURE));
 
         return "ldap" + (ldapServerSecure ? "s" : "") +  "://" + ldapServerIP + ":" + ldapServerPort;
     }

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

@@ -0,0 +1,65 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 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.configguide;
+
+import password.pwm.config.PwmSetting;
+
+public enum ConfigGuideFormField {
+    PARAM_TEMPLATE_LDAP(PwmSetting.TEMPLATE_LDAP),
+    PARAM_TEMPLATE_STORAGE(PwmSetting.TEMPLATE_STORAGE),
+
+    PARAM_APP_SITEURL(PwmSetting.PWM_SITE_URL),
+
+    PARAM_LDAP_HOST(null),
+    PARAM_LDAP_PORT(null),
+    PARAM_LDAP_SECURE(null),
+    PARAM_LDAP_PROXY_DN(PwmSetting.LDAP_PROXY_USER_DN),
+    PARAM_LDAP_PROXY_PW(PwmSetting.LDAP_PROXY_USER_PASSWORD),
+
+    PARAM_LDAP_CONTEXT(PwmSetting.LDAP_CONTEXTLESS_ROOT),
+    PARAM_LDAP_TEST_USER(PwmSetting.LDAP_TEST_USER_DN),
+    PARAM_LDAP_ADMIN_GROUP(PwmSetting.QUERY_MATCH_PWM_ADMIN),
+
+    PARAM_DB_CLASSNAME(PwmSetting.DATABASE_CLASS),
+    PARAM_DB_CONNECT_URL(PwmSetting.DATABASE_URL),
+    PARAM_DB_USERNAME(PwmSetting.DATABASE_USERNAME),
+    PARAM_DB_PASSWORD(PwmSetting.DATABASE_PASSWORD),
+    PARAM_DB_VENDOR(PwmSetting.DB_VENDOR_TEMPLATE),
+
+    PARAM_TELEMETRY_ENABLE(PwmSetting.PUBLISH_STATS_ENABLE),
+    PARAM_TELEMETRY_DESCRIPTION(PwmSetting.PUBLISH_STATS_SITE_DESCRIPTION),
+
+    PARAM_CONFIG_PASSWORD(null),
+
+    ;
+
+    private final PwmSetting pwmSetting;
+
+    ConfigGuideFormField(final PwmSetting pwmSetting) {
+        this.pwmSetting = pwmSetting;
+    }
+
+    public PwmSetting getPwmSetting() {
+        return pwmSetting;
+    }
+}

+ 15 - 14
src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -171,11 +171,11 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             return;
         }
 
-        if (!configGuideBean.getFormData().containsKey(ConfigGuideForm.FormParameter.PARAM_APP_SITEURL)) {
+        if (!configGuideBean.getFormData().containsKey(ConfigGuideFormField.PARAM_APP_SITEURL)) {
             final URI uri = URI.create(pwmRequest.getHttpServletRequest().getRequestURL().toString());
             final int port = PwmURL.portForUriSchema(uri);
             final String newUri = uri.getScheme() + "://" + uri.getHost() + ":" + port + pwmRequest.getContextPath();
-            configGuideBean.getFormData().put(ConfigGuideForm.FormParameter.PARAM_APP_SITEURL,newUri);
+            configGuideBean.getFormData().put(ConfigGuideFormField.PARAM_APP_SITEURL,newUri);
         }
 
         if (configGuideBean.getStep() == GuideStep.LDAP_CERT) {
@@ -374,7 +374,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             break;
 
             case LDAP_TESTUSER: {
-                final String testUserValue = configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER);
+                final String testUserValue = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_TEST_USER);
                 if (testUserValue != null && !testUserValue.isEmpty()) {
                     records.addAll(ldapStatusChecker.checkBasicLdapConnectivity(tempApplication, tempConfiguration, ldapProfile, false));
                     records.addAll(ldapStatusChecker.doLdapTestUserCheck(tempConfiguration, ldapProfile, tempApplication));
@@ -466,7 +466,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             throws IOException, PwmUnrecoverableException
     {
         final String bodyString = pwmRequest.readRequestBodyAsString();
-        final Map<ConfigGuideForm.FormParameter,String> incomingFormData = JsonUtil.deserialize(bodyString, new TypeToken<Map<ConfigGuideForm.FormParameter, String>>() {
+        final Map<ConfigGuideFormField,String> incomingFormData = JsonUtil.deserialize(bodyString, new TypeToken<Map<ConfigGuideFormField, String>>() {
         });
 
         if (incomingFormData != null) {
@@ -489,7 +489,8 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         }
 
         if (GuideStep.START.equals(requestedStep)) {
-            configGuideBean.setFormData(ConfigGuideForm.defaultForm());
+            configGuideBean.getFormData().clear();
+            configGuideBean.getFormData().putAll(ConfigGuideForm.defaultForm());
         }
 
         if ("NEXT".equals(requestedStep)) {
@@ -545,7 +546,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             final ConfigGuideBean configGuideBean
     ) throws PwmOperationalException, PwmUnrecoverableException {
         final StoredConfigurationImpl storedConfiguration = ConfigGuideForm.generateStoredConfig(configGuideBean);
-        final String configPassword = configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_CONFIG_PASSWORD);
+        final String configPassword = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_CONFIG_PASSWORD);
         if (configPassword != null && configPassword.length() > 0) {
             storedConfiguration.setPassword(configPassword);
         } else {
@@ -599,11 +600,11 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
     }
 
     public static SchemaOperationResult extendSchema(final ConfigGuideBean configGuideBean, final boolean doSchemaExtension) {
-        final Map<ConfigGuideForm.FormParameter,String> form = configGuideBean.getFormData();
-        final boolean ldapServerSecure = "true".equalsIgnoreCase(form.get(ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE));
-        final String ldapUrl = "ldap" + (ldapServerSecure ? "s" : "") + "://" + form.get(ConfigGuideForm.FormParameter.PARAM_LDAP_HOST) + ":" + form.get(ConfigGuideForm.FormParameter.PARAM_LDAP_PORT);
+        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(ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN), form.get(ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_PW));
+            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) {
@@ -633,9 +634,9 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
 
 
     private void checkLdapServer(final ConfigGuideBean configGuideBean) throws PwmOperationalException, IOException {
-        final Map<ConfigGuideForm.FormParameter,String> formData = configGuideBean.getFormData();
-        final String host = formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_HOST);
-        final int port = Integer.parseInt(formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_PORT));
+        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);
@@ -646,7 +647,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             socket.connect(socketAddress, timeout);
         }
 
-        if (Boolean.parseBoolean(formData.get(ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE))) {
+        if (Boolean.parseBoolean(formData.get(ConfigGuideFormField.PARAM_LDAP_SECURE))) {
             X509Utils.readRemoteCertificates(host, port);
         }
     }

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

@@ -8,7 +8,7 @@
  * 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.
+ * (at your option) any later version.g
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -22,6 +22,9 @@
 
 package password.pwm.http.servlet.configguide;
 
+import password.pwm.PwmConstants;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.ConfigGuideBean;
@@ -31,6 +34,8 @@ import java.util.Set;
 
 public enum GuideStep {
     START(null),
+    EULA(EulaVisibilityCheck.class),
+    TELEMETRY(TelemetryVisibilityCheck.class),
     TEMPLATE(null),
     LDAP_SERVER(null),
     LDAP_CERT(null),
@@ -107,4 +112,17 @@ public enum GuideStep {
             }
         }
     }
+
+    static class EulaVisibilityCheck implements VisibilityCheck {
+        public boolean visible(final ConfigGuideBean configGuideBean) {
+            return PwmConstants.ENABLE_EULA_DISPLAY;
+        }
+    }
+
+    static class TelemetryVisibilityCheck implements VisibilityCheck {
+        public boolean visible(final ConfigGuideBean configGuideBean) {
+            return !PwmSetting.PUBLISH_STATS_ENABLE.isHidden() &&
+                    !PwmSettingCategory.TELEMETRY.isHidden();
+        }
+    }
 }

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

@@ -149,23 +149,34 @@ class NewUserFormUtils {
         return ldapData;
     }
 
-    static NewUserForm injectRemoteValuesIntoForm(
+    static void injectRemoteValuesIntoForm(final NewUserBean newUserBean, final NewUserProfile newUserProfile)
+            throws PwmUnrecoverableException
+    {
+        final NewUserForm oldForm = newUserBean.getNewUserForm();
+        final List<FormConfiguration> formConfigurations = newUserProfile.readSettingAsForm(PwmSetting.NEWUSER_FORM);
+        final Map<FormConfiguration,String> userFormValues = FormUtility.asFormConfigurationMap(formConfigurations, oldForm.getFormData());
+        final Map<String,String> injectedValues = newUserBean.getRemoteInputData();
+        final NewUserForm newUserForm = injectRemoteValuesIntoForm(userFormValues, injectedValues, newUserProfile, oldForm.getNewUserPassword(), oldForm.getConfirmPassword());
+        newUserBean.setNewUserForm(newUserForm);
+    }
+
+    private static NewUserForm injectRemoteValuesIntoForm(
             final Map<FormConfiguration, String> userFormValues,
             final Map<String,String> injectedValues,
             final NewUserProfile newUserProfile,
             final PasswordData passwordData1,
             final PasswordData passwordData2
     ) {
-        final Map<String,String> newFormValues = new HashMap<>();
+        final Map<String,String> newFormValues = new LinkedHashMap<>();
         newFormValues.putAll(FormUtility.asStringMap(userFormValues));
 
         final List<FormConfiguration> formConfigurations = newUserProfile.readSettingAsForm(PwmSetting.NEWUSER_FORM);
         if (injectedValues != null) {
             for (final FormConfiguration formConfiguration : formConfigurations) {
                 final String name = formConfiguration.getName();
-                if (formConfiguration.isReadonly()
-                        || !newFormValues.containsKey(name) && injectedValues.containsKey(name))
-                {
+                final boolean formHasValue = !StringUtil.isEmpty(newFormValues.get(name));
+
+                if (formConfiguration.isReadonly() || (!formHasValue && injectedValues.containsKey(name))) {
                     newFormValues.put(formConfiguration.getName(), injectedValues.get(formConfiguration.getName()));
                 }
             }

+ 62 - 13
src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -69,6 +69,7 @@ import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -191,25 +192,43 @@ public class NewUserServlet extends ControlledPwmServlet {
                 return;
             }
 
-            if (newUserProfileIDs.size() == 1) {
+            final LinkedHashMap<String,String> visibleProfiles = new LinkedHashMap<>(NewUserUtils.figureDisplayableProfiles(pwmRequest));
+
+            if (visibleProfiles.size() == 1) {
                 final String singleID =  newUserProfileIDs.iterator().next();
                 LOGGER.trace(pwmRequest, "only one new user profile is defined, auto-selecting profile " + singleID);
                 newUserBean.setProfileID(singleID);
             } else {
                 LOGGER.trace(pwmRequest, "new user profile not yet selected, redirecting to choice page");
+                pwmRequest.setAttribute(PwmRequestAttribute.NewUser_VisibleProfiles, visibleProfiles);
                 pwmRequest.forwardToJsp(JspUrl.NEW_USER_PROFILE_CHOICE);
                 return;
             }
         }
 
         final NewUserProfile newUserProfile = getNewUserProfile(pwmRequest);
+        if (newUserBean.getCreateStartTime() != null) {
+            forwardToWait(pwmRequest, newUserProfile);
+            return;
+        }
+
 
         // try to read the new user policy to make sure it's readable, that way an exception is thrown here instead of by the jsp
         newUserProfile.getNewUserPasswordPolicy(pwmApplication, pwmSession.getSessionStateBean().getLocale());//
 
-        if (newUserBean.getNewUserForm() == null) {
-            forwardToFormPage(pwmRequest, newUserBean);
-            return;
+        if (!newUserBean.isFormPassed()) {
+            if (showFormPage(newUserProfile)) {
+                forwardToFormPage(pwmRequest, newUserBean);
+                return;
+            } else {
+                NewUserFormUtils.injectRemoteValuesIntoForm(newUserBean, newUserProfile);
+                try {
+                    verifyForm(pwmRequest, newUserBean.getNewUserForm(), false);
+                } catch (PwmDataValidationException e) {
+                    throw new PwmUnrecoverableException(e.getErrorInformation());
+                }
+                newUserBean.setFormPassed(true);
+            }
         }
 
         final TokenVerificationProgress tokenVerificationProgress = newUserBean.getTokenVerificationProgress();
@@ -237,7 +256,7 @@ public class NewUserServlet extends ControlledPwmServlet {
 
         final String newUserAgreementText = newUserProfile.readSettingAsLocalizedString(PwmSetting.NEWUSER_AGREEMENT_MESSAGE,
                 pwmSession.getSessionStateBean().getLocale());
-        if (newUserAgreementText != null && !newUserAgreementText.isEmpty()) {
+        if (!StringUtil.isEmpty(newUserAgreementText)) {
             if (!newUserBean.isAgreementPassed()) {
                 final MacroMachine macroMachine = NewUserUtils.createMacroMachineForNewUser(
                         pwmApplication,
@@ -251,17 +270,13 @@ public class NewUserServlet extends ControlledPwmServlet {
             }
         }
 
-        if (!newUserBean.isFormPassed()) {
-            forwardToFormPage(pwmRequest, newUserBean);
-        }
-
         // success so create the new user.
         final String newUserDN = NewUserUtils.determineUserDN(pwmRequest, newUserBean.getNewUserForm());
 
         try {
             NewUserUtils.createUser(newUserBean.getNewUserForm(), pwmRequest, newUserDN);
             newUserBean.setCreateStartTime(Instant.now());
-            pwmRequest.forwardToJsp(JspUrl.NEW_USER_WAIT);
+            forwardToWait(pwmRequest, newUserProfile);
         } catch (PwmOperationalException e) {
             LOGGER.error(pwmRequest, "error during user creation: " + e.getMessage());
             if (newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_DELETE_ON_FAIL)) {
@@ -272,6 +287,18 @@ public class NewUserServlet extends ControlledPwmServlet {
         }
     }
 
+    private boolean showFormPage(final NewUserProfile profile) {
+        final boolean promptForPassword = profile.readSettingAsBoolean(PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD);
+        boolean formNeedsShowing = false;
+        final List<FormConfiguration> formConfigurations = profile.readSettingAsForm(PwmSetting.NEWUSER_FORM);
+        for (final FormConfiguration formConfiguration : formConfigurations) {
+            if (formConfiguration.getType() != FormConfiguration.Type.hidden) {
+                formNeedsShowing = true;
+            }
+        }
+        return formNeedsShowing || promptForPassword;
+    }
+
     private boolean readProfileFromUrl(final PwmRequest pwmRequest, final NewUserBean newUserBean)
             throws PwmUnrecoverableException, ServletException, IOException
     {
@@ -350,12 +377,17 @@ public class NewUserServlet extends ControlledPwmServlet {
         final Map<FormConfiguration,String> formValueData = FormUtility.readFormValuesFromMap(newUserForm.getFormData(), formDefinition, locale);
 
         FormUtility.validateFormValues(pwmApplication.getConfig(), formValueData, locale);
+        final List<FormUtility.ValidationFlag> validationFlags = new ArrayList<>();
+        validationFlags.add(FormUtility.ValidationFlag.checkReadOnlyAndHidden);
+        if (allowResultCaching) {
+            validationFlags.add(FormUtility.ValidationFlag.allowResultCaching);
+        }
         FormUtility.validateFormValueUniqueness(
                 pwmApplication,
                 formValueData,
                 locale,
                 Collections.emptyList(),
-                allowResultCaching
+                validationFlags.toArray(new FormUtility.ValidationFlag[validationFlags.size()])
         );
         final UserInfo uiBean = UserInfoBean.builder()
                 .cachedPasswordRuleAttributes(FormUtility.asStringMap(formValueData))
@@ -568,7 +600,7 @@ public class NewUserServlet extends ControlledPwmServlet {
         pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, NewUserBean.class);
         pwmRequest.sendRedirectToContinue();
 
-        return ProcessStatus.Continue;
+        return ProcessStatus.Halt;
     }
 
     @ActionHandler(action = "complete")
@@ -598,7 +630,7 @@ public class NewUserServlet extends ControlledPwmServlet {
         pwmRequest.getPwmApplication().getSessionStateService().clearBean(pwmRequest, NewUserBean.class);
 
         final String configuredRedirectUrl = newUserProfile.readSettingAsString(PwmSetting.NEWUSER_REDIRECT_URL);
-        if (!StringUtil.isEmpty(configuredRedirectUrl)) {
+        if (!StringUtil.isEmpty(configuredRedirectUrl) && StringUtil.isEmpty(pwmRequest.getPwmSession().getSessionStateBean().getForwardURL())) {
             final MacroMachine macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine(pwmRequest.getPwmApplication());
             final String macroedUrl = macroMachine.expandMacros(configuredRedirectUrl);
             pwmRequest.sendRedirect(macroedUrl);
@@ -623,6 +655,23 @@ public class NewUserServlet extends ControlledPwmServlet {
         return pwmRequest.getConfig().getNewUserProfiles().get(profileID);
     }
 
+    private void forwardToWait(final PwmRequest pwmRequest, final NewUserProfile newUserProfile)
+            throws ServletException, PwmUnrecoverableException, IOException
+    {
+        final long pauseSeconds = newUserProfile.readSettingAsLong(PwmSetting.NEWUSER_MINIMUM_WAIT_TIME);
+        if (pauseSeconds > 0) {
+            pwmRequest.forwardToJsp(JspUrl.NEW_USER_WAIT);
+        } else {
+            final String newUserServletUrl = pwmRequest.getContextPath() + PwmServletDefinition.NewUser.servletUrl();
+            final String redirectUrl = PwmURL.appendAndEncodeUrlParameters(
+                    newUserServletUrl,
+                    Collections.singletonMap(PwmConstants.PARAM_ACTION_REQUEST,NewUserAction.complete.name())
+            );
+            pwmRequest.sendRedirect(redirectUrl);
+        }
+    }
+
+
     private void forwardToFormPage(final PwmRequest pwmRequest, final NewUserBean newUserBean)
             throws ServletException, PwmUnrecoverableException, IOException
     {

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

@@ -75,6 +75,7 @@ import password.pwm.ws.client.rest.RestTokenDataClient;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
@@ -555,4 +556,15 @@ class NewUserUtils {
                 JavaHelper.unhandledSwitchStatement(tokenType);
         }
     }
+
+    static Map<String,String> figureDisplayableProfiles(final PwmRequest pwmRequest) {
+        final Map<String,String> returnMap = new LinkedHashMap<>();
+        for (final NewUserProfile newUserProfile : pwmRequest.getConfig().getNewUserProfiles().values()) {
+            final boolean visible = newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_PROFILE_DISPLAY_VISIBLE);
+            if (visible) {
+                returnMap.put(newUserProfile.getIdentifier(), newUserProfile.getDisplayName(pwmRequest.getLocale()));
+            }
+        }
+        return Collections.unmodifiableMap(returnMap);
+    }
 }

+ 6 - 24
src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartDataBean.java

@@ -22,36 +22,18 @@
 
 package password.pwm.http.servlet.peoplesearch;
 
+import lombok.Getter;
+import lombok.Setter;
+
 import java.io.Serializable;
 import java.util.Collections;
 import java.util.List;
 
+@Getter
+@Setter
 class OrgChartDataBean implements Serializable {
     private OrgChartReferenceBean parent;
     private OrgChartReferenceBean self;
+    private OrgChartReferenceBean assistant;
     private List<OrgChartReferenceBean> children = Collections.emptyList();
-
-    public OrgChartReferenceBean getParent() {
-        return parent;
-    }
-
-    public void setParent(final OrgChartReferenceBean parent) {
-        this.parent = parent;
-    }
-
-    public OrgChartReferenceBean getSelf() {
-        return self;
-    }
-
-    public void setSelf(final OrgChartReferenceBean self) {
-        this.self = self;
-    }
-
-    public List<OrgChartReferenceBean> getChildren() {
-        return children;
-    }
-
-    public void setChildren(final List<OrgChartReferenceBean> children) {
-        this.children = children;
-    }
 }

+ 5 - 24
src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartReferenceBean.java

@@ -22,36 +22,17 @@
 
 package password.pwm.http.servlet.peoplesearch;
 
+import lombok.Getter;
+import lombok.Setter;
+
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
+@Getter
+@Setter
 class OrgChartReferenceBean implements Serializable {
     public String userKey;
     public List<String> displayNames = new ArrayList<>();
     public String photoURL;
-
-    public String getPhotoURL() {
-        return photoURL;
-    }
-
-    public void setPhotoURL(final String photoURL) {
-        this.photoURL = photoURL;
-    }
-
-    public String getUserKey() {
-        return userKey;
-    }
-
-    public void setUserKey(final String userKey) {
-        this.userKey = userKey;
-    }
-
-    public List<String> getDisplayNames() {
-        return displayNames;
-    }
-
-    public void setDisplayNames(final List<String> displayNames) {
-        this.displayNames = displayNames;
-    }
 }

+ 26 - 40
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java

@@ -22,49 +22,35 @@
 
 package password.pwm.http.servlet.peoplesearch;
 
+import lombok.Getter;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 
+@Getter
 public class PeopleSearchConfiguration {
-    private final String photoAttribute;
-    private final String photoUrlOverride;
-    private final boolean photosEnabled;
-    private final boolean orgChartEnabled;
-    private final String orgChartParentAttr;
-    private final String orgChartChildAttr;
-
-    public PeopleSearchConfiguration(final Configuration configuration) {
-        photoAttribute = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_ATTRIBUTE);
-        photoUrlOverride = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
-        photosEnabled = (photoAttribute != null && !photoAttribute.isEmpty())
-                || (photoUrlOverride != null && !photoUrlOverride.isEmpty());
-
-        orgChartParentAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
-        orgChartChildAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
-        orgChartEnabled = orgChartParentAttr != null && !orgChartParentAttr.isEmpty() && orgChartChildAttr != null && !orgChartChildAttr.isEmpty();
-    }
-
-    public String getPhotoAttribute() {
-        return photoAttribute;
-    }
-
-    public String getPhotoUrlOverride() {
-        return photoUrlOverride;
-    }
-
-    public boolean isPhotosEnabled() {
-        return photosEnabled;
-    }
-
-    public boolean isOrgChartEnabled() {
-        return orgChartEnabled;
-    }
-
-    public String getOrgChartParentAttr() {
-        return orgChartParentAttr;
-    }
-
-    public String getOrgChartChildAttr() {
-        return orgChartChildAttr;
+    private String photoAttribute;
+    private String photoUrlOverride;
+    private boolean photosEnabled;
+    private boolean orgChartEnabled;
+    private String orgChartParentAttr;
+    private String orgChartChildAttr;
+    private String orgChartAssistantAttr;
+
+    public static PeopleSearchConfiguration fromConfiguration(final Configuration configuration) {
+        final PeopleSearchConfiguration config = new PeopleSearchConfiguration();
+        config.photoAttribute = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_ATTRIBUTE);
+        config.photoUrlOverride = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
+        config.photosEnabled = (config.photoAttribute != null && !config.photoAttribute.isEmpty())
+                || (config.photoUrlOverride != null && !config.photoUrlOverride.isEmpty());
+
+        config.orgChartAssistantAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_ASSISTANT_ATTRIBUTE);
+        config.orgChartParentAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
+        config.orgChartChildAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
+        config.orgChartEnabled = config.orgChartParentAttr != null
+                && !config.orgChartParentAttr.isEmpty()
+                && config.orgChartChildAttr != null
+                && !config.orgChartChildAttr.isEmpty();
+
+        return config;
     }
 }

+ 19 - 8
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java

@@ -74,18 +74,18 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
 
-public class PeopleSearchDataReader {
+class PeopleSearchDataReader {
     private static final PwmLogger LOGGER = PwmLogger.forClass(PeopleSearchDataReader.class);
 
     private final PwmRequest pwmRequest;
     private final PeopleSearchConfiguration config;
 
-    public PeopleSearchDataReader(final PwmRequest pwmRequest) {
+    PeopleSearchDataReader(final PwmRequest pwmRequest) {
         this.pwmRequest = pwmRequest;
-        this.config= new PeopleSearchConfiguration(pwmRequest.getConfig());
+        this.config = PeopleSearchConfiguration.fromConfiguration(pwmRequest.getConfig());
     }
 
-    public SearchResultBean makeSearchResultBean(
+    SearchResultBean makeSearchResultBean(
             final String searchData,
             final boolean includeDisplayName
     )
@@ -113,7 +113,7 @@ public class PeopleSearchDataReader {
         return searchResultBean;
     }
 
-    public OrgChartDataBean makeOrgChartData(
+    OrgChartDataBean makeOrgChartData(
             final UserIdentity userIdentity,
             final boolean noChildren
 
@@ -166,13 +166,24 @@ public class PeopleSearchDataReader {
             orgChartData.setChildren(Collections.unmodifiableList(new ArrayList<>(sortedChildren.values())));
         }
 
+        if (!StringUtil.isEmpty(config.getOrgChartAssistantAttr())) {
+            final List<UserIdentity> assistantIdentities = readUserDNAttributeValues(userIdentity, config.getOrgChartAssistantAttr());
+            if (assistantIdentities != null && !assistantIdentities.isEmpty()) {
+                final UserIdentity assistantIdentity = assistantIdentities.iterator().next();
+                final OrgChartReferenceBean assistantReference = makeOrgChartReferenceForIdentity(assistantIdentity);
+                if (assistantReference != null) {
+                    orgChartData.setAssistant(assistantReference);
+                }
+            }
+        }
+
         final TimeDuration totalTime = TimeDuration.fromCurrent(startTime);
         storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, orgChartData);
         LOGGER.trace(pwmRequest, "completed makeOrgChartData in " + totalTime.asCompactString() + " with " + childCount + " children" );
         return orgChartData;
     }
 
-    public UserDetailBean makeUserDetailRequest(
+    UserDetailBean makeUserDetailRequest(
             final String userKey
     )
             throws PwmUnrecoverableException, IOException, ServletException, PwmOperationalException, ChaiUnavailableException
@@ -513,7 +524,7 @@ public class PeopleSearchDataReader {
         return new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userInfo, null);
     }
 
-    public void checkIfUserIdentityViewable(
+    void checkIfUserIdentityViewable(
             final UserIdentity userIdentity
     )
             throws  PwmUnrecoverableException, PwmOperationalException
@@ -594,7 +605,7 @@ public class PeopleSearchDataReader {
         }
     }
 
-    public PhotoDataBean readPhotoDataFromLdap(
+    PhotoDataBean readPhotoDataFromLdap(
             final UserIdentity userIdentity
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException

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

@@ -110,7 +110,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet {
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
-        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration(pwmRequest.getConfig());
+        final PeopleSearchConfiguration peopleSearchConfiguration = PeopleSearchConfiguration.fromConfiguration(pwmRequest.getConfig());
 
         final Map<String, String> searchColumns = new LinkedHashMap<>();
         final List<FormConfiguration> searchForm = pwmRequest.getConfig().readSettingAsForm(PwmSetting.PEOPLE_SEARCH_RESULT_FORM);
@@ -161,7 +161,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet {
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration(pwmRequest.getConfig());
+        final PeopleSearchConfiguration peopleSearchConfiguration = PeopleSearchConfiguration.fromConfiguration(pwmRequest.getConfig());
 
         if (!peopleSearchConfiguration.isOrgChartEnabled()) {
             throw new PwmUnrecoverableException(PwmError.ERROR_SERVICE_NOT_AVAILABLE);

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

@@ -416,7 +416,7 @@ public enum PwmIfTest {
                 return false;
             }
 
-            return new PeopleSearchConfiguration(pwmRequest.getConfig()).isOrgChartEnabled();
+            return PeopleSearchConfiguration.fromConfiguration(pwmRequest.getConfig()).isOrgChartEnabled();
         }
     }
 

+ 1 - 1
src/main/java/password/pwm/ldap/search/UserSearchEngine.java

@@ -128,7 +128,7 @@ public class UserSearchEngine implements PwmService {
 
     @Override
     public ServiceInfoBean serviceInfo() {
-        return new ServiceInfoBean(Collections.emptyList());
+        return new ServiceInfoBean(Collections.emptyList(),debugProperties());
     }
 
     public UserIdentity resolveUsername(

+ 87 - 0
src/main/java/password/pwm/svc/PwmServiceEnum.java

@@ -0,0 +1,87 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 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.svc;
+
+import password.pwm.util.java.JavaHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public enum PwmServiceEnum {
+    SecureService(          password.pwm.util.secure.SecureService.class,           Flag.StartDuringRuntimeInstance),
+    LdapConnectionService(  password.pwm.ldap.LdapConnectionService.class,          Flag.StartDuringRuntimeInstance),
+    DatabaseService(        password.pwm.util.db.DatabaseService.class,             Flag.StartDuringRuntimeInstance),
+    SharedHistoryManager(   password.pwm.svc.wordlist.SharedHistoryManager.class),
+    AuditService(           password.pwm.svc.event.AuditService.class),
+    StatisticsManager(      password.pwm.svc.stats.StatisticsManager.class),
+    WordlistManager(        password.pwm.svc.wordlist.WordlistManager.class),
+    SeedlistManager(        password.pwm.svc.wordlist.SeedlistManager.class),
+    EmailQueueManager(      password.pwm.util.queue.EmailQueueManager.class),
+    SmsQueueManager(        password.pwm.util.queue.SmsQueueManager.class),
+    UrlShortenerService(    password.pwm.svc.shorturl.UrlShortenerService.class),
+    TokenService(           password.pwm.svc.token.TokenService.class),
+    VersionChecker(         password.pwm.svc.telemetry.VersionChecker.class),
+    IntruderManager(        password.pwm.svc.intruder.IntruderManager.class),
+    CrService(              password.pwm.util.operations.CrService.class,           Flag.StartDuringRuntimeInstance),
+    OtpService(             password.pwm.util.operations.OtpService.class),
+    CacheService(           password.pwm.svc.cache.CacheService.class,              Flag.StartDuringRuntimeInstance),
+    HealthMonitor(          password.pwm.health.HealthMonitor.class),
+    ReportService(          password.pwm.svc.report.ReportService.class,            Flag.StartDuringRuntimeInstance),
+    ResourceServletService( password.pwm.http.servlet.resource.ResourceServletService.class),
+    SessionTrackService(    password.pwm.svc.sessiontrack.SessionTrackService.class),
+    SessionStateSvc(        password.pwm.http.state.SessionStateService.class),
+    UserSearchEngine(       password.pwm.ldap.search.UserSearchEngine.class,        Flag.StartDuringRuntimeInstance),
+    TelemetryService(       password.pwm.svc.telemetry.TelemetryService.class),
+    ClusterService(         password.pwm.svc.cluster.ClusterService.class),
+
+    ;
+
+    private final Class<? extends PwmService> clazz;
+    private final Flag[] flags;
+
+    private enum Flag {
+        StartDuringRuntimeInstance,
+    }
+
+    PwmServiceEnum(final Class<? extends PwmService> clazz, final Flag... flags) {
+        this.clazz = clazz;
+        this.flags = flags;
+    }
+
+    public boolean isInternalRuntime() {
+        return JavaHelper.enumArrayContainsValue(flags, Flag.StartDuringRuntimeInstance);
+    }
+
+    static List<Class<? extends PwmService>> allClasses() {
+        final List<Class<? extends PwmService>> pwmServiceClasses = new ArrayList<>();
+        for (final PwmServiceEnum enumClass : values()) {
+            pwmServiceClasses.add(enumClass.getPwmServiceClass());
+        }
+        return Collections.unmodifiableList(pwmServiceClasses);
+    }
+
+    public Class<? extends PwmService> getPwmServiceClass() {
+        return clazz;
+    }
+}

+ 2 - 79
src/main/java/password/pwm/svc/PwmServiceManager.java

@@ -28,32 +28,8 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.health.HealthMonitor;
-import password.pwm.http.servlet.resource.ResourceServletService;
-import password.pwm.http.state.SessionStateService;
-import password.pwm.ldap.LdapConnectionService;
-import password.pwm.ldap.search.UserSearchEngine;
-import password.pwm.svc.cache.CacheService;
-import password.pwm.svc.cluster.ClusterService;
-import password.pwm.svc.event.AuditService;
-import password.pwm.svc.intruder.IntruderManager;
-import password.pwm.svc.report.ReportService;
-import password.pwm.svc.sessiontrack.SessionTrackService;
-import password.pwm.svc.shorturl.UrlShortenerService;
-import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.svc.token.TokenService;
-import password.pwm.svc.wordlist.SeedlistManager;
-import password.pwm.svc.wordlist.SharedHistoryManager;
-import password.pwm.svc.wordlist.WordlistManager;
-import password.pwm.util.VersionChecker;
-import password.pwm.util.db.DatabaseService;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.operations.CrService;
-import password.pwm.util.operations.OtpService;
-import password.pwm.util.queue.EmailQueueManager;
-import password.pwm.util.queue.SmsQueueManager;
-import password.pwm.util.secure.SecureService;
 
 import java.time.Instant;
 import java.util.ArrayList;
@@ -71,59 +47,6 @@ public class PwmServiceManager {
     private final Map<Class<? extends PwmService>, PwmService> runningServices = new HashMap<>();
     private boolean initialized;
 
-    public enum PwmServiceClassEnum {
-        SecureService(          SecureService.class,             true),
-        LdapConnectionService(  LdapConnectionService.class,     true),
-        DatabaseService(        DatabaseService.class,           true),
-        SharedHistoryManager(   SharedHistoryManager.class,      false),
-        AuditService(           AuditService.class,              false),
-        StatisticsManager(      StatisticsManager.class,         false),
-        WordlistManager(        WordlistManager.class,           false),
-        SeedlistManager(        SeedlistManager.class,           false),
-        EmailQueueManager(      EmailQueueManager.class,         false),
-        SmsQueueManager(        SmsQueueManager.class,           false),
-        UrlShortenerService(    UrlShortenerService.class,       false),
-        TokenService(           TokenService.class,              false),
-        VersionChecker(         VersionChecker.class,            false),
-        IntruderManager(        IntruderManager.class,           false),
-        CrService(              CrService.class,                 true),
-        OtpService(             OtpService.class,                false),
-        CacheService(           CacheService.class,              true),
-        HealthMonitor(          HealthMonitor.class,             false),
-        ReportService(          ReportService.class,             true),
-        ResourceServletService( ResourceServletService.class,    false),
-        SessionTrackService(    SessionTrackService.class,       false),
-        SessionStateSvc(        SessionStateService.class,       false),
-        UserSearchEngine(       UserSearchEngine.class,          true),
-        ClusterService(         ClusterService.class,            false),
-
-        ;
-
-        private final Class<? extends PwmService> clazz;
-        private final boolean internalRuntime;
-
-        PwmServiceClassEnum(final Class<? extends PwmService> clazz, final boolean internalRuntime) {
-            this.clazz = clazz;
-            this.internalRuntime = internalRuntime;
-        }
-
-        public boolean isInternalRuntime() {
-            return internalRuntime;
-        }
-
-        static List<Class<? extends PwmService>> allClasses() {
-            final List<Class<? extends PwmService>> pwmServiceClasses = new ArrayList<>();
-            for (final PwmServiceClassEnum enumClass : values()) {
-                pwmServiceClasses.add(enumClass.getPwmServiceClass());
-            }
-            return Collections.unmodifiableList(pwmServiceClasses);
-        }
-
-        public Class<? extends PwmService> getPwmServiceClass() {
-            return clazz;
-        }
-    }
-
     public PwmServiceManager(final PwmApplication pwmApplication) {
         this.pwmApplication = pwmApplication;
     }
@@ -139,7 +62,7 @@ public class PwmServiceManager {
         final boolean internalRuntimeInstance = pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
                 || pwmApplication.getPwmEnvironment().getFlags().contains(PwmEnvironment.ApplicationFlag.CommandLineInstance);
 
-        for (final PwmServiceClassEnum serviceClassEnum : PwmServiceClassEnum.values()) {
+        for (final PwmServiceEnum serviceClassEnum : PwmServiceEnum.values()) {
             boolean startService = true;
             if (internalRuntimeInstance && !serviceClassEnum.isInternalRuntime()) {
                 startService = false;
@@ -192,7 +115,7 @@ public class PwmServiceManager {
             return;
         }
 
-        final List<Class<? extends PwmService>> reverseServiceList = new ArrayList<>(PwmServiceClassEnum.allClasses());
+        final List<Class<? extends PwmService>> reverseServiceList = new ArrayList<>(PwmServiceEnum.allClasses());
         Collections.reverse(reverseServiceList);
         for (final Class<? extends PwmService> serviceClass : reverseServiceList) {
             if (runningServices.containsKey(serviceClass)) {

+ 11 - 106
src/main/java/password/pwm/svc/stats/StatisticsManager.java

@@ -23,22 +23,12 @@
 package password.pwm.svc.stats;
 
 import org.apache.commons.csv.CSVPrinter;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
 import password.pwm.PwmApplication;
-import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
-import password.pwm.bean.StatsPublishBean;
-import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.PwmException;
-import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.client.PwmHttpClient;
 import password.pwm.svc.PwmService;
 import password.pwm.util.AlertHandler;
 import password.pwm.util.java.JavaHelper;
@@ -47,13 +37,10 @@ import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmRandom;
 
 import java.io.IOException;
 import java.io.OutputStream;
 import java.math.BigDecimal;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.time.Instant;
@@ -67,8 +54,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
-import java.util.Timer;
 import java.util.TimerTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 public class StatisticsManager implements PwmService {
 
@@ -86,14 +74,13 @@ public class StatisticsManager implements PwmService {
 
     public static final String KEY_CURRENT = "CURRENT";
     public static final String KEY_CUMULATIVE = "CUMULATIVE";
-    public static final String KEY_CLOUD_PUBLISH_TIMESTAMP = "CLOUD_PUB_TIMESTAMP";
 
     private LocalDB localDB;
 
     private DailyKey currentDailyKey = new DailyKey(new Date());
     private DailyKey initialDailyKey = new DailyKey(new Date());
 
-    private Timer daemonTimer;
+    private ScheduledExecutorService executorService;
 
     private final StatisticsBundle statsCurrent = new StatisticsBundle();
     private StatisticsBundle statsDaily = new StatisticsBundle();
@@ -297,28 +284,10 @@ public class StatisticsManager implements PwmService {
         localDB.put(LocalDB.DB.PWM_STATS, DB_KEY_INITIAL_DAILY_KEY, initialDailyKey.toString());
 
         { // setup a timer to roll over at 0 Zula and one to write current stats every 10 seconds
-            final String threadName = JavaHelper.makeThreadName(pwmApplication, this.getClass()) + " timer";
-            daemonTimer = new Timer(threadName, true);
-            daemonTimer.schedule(new FlushTask(), 10 * 1000, DB_WRITE_FREQUENCY_MS);
-            daemonTimer.schedule(new NightlyTask(), Date.from(JavaHelper.nextZuluZeroTime()));
-        }
-
-        if (pwmApplication.getApplicationMode() == PwmApplicationMode.RUNNING) {
-            if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PUBLISH_STATS_ENABLE)) {
-                long lastPublishTimestamp = pwmApplication.getInstallTime().toEpochMilli();
-                {
-                    final String lastPublishDateStr = localDB.get(LocalDB.DB.PWM_STATS,KEY_CLOUD_PUBLISH_TIMESTAMP);
-                    if (lastPublishDateStr != null && lastPublishDateStr.length() > 0) {
-                        try {
-                            lastPublishTimestamp = Long.parseLong(lastPublishDateStr);
-                        } catch (Exception e) {
-                            LOGGER.error("unexpected error reading last publish timestamp from PwmDB: " + e.getMessage());
-                        }
-                    }
-                }
-                final Date nextPublishTime = new Date(lastPublishTimestamp + PwmConstants.STATISTICS_PUBLISH_FREQUENCY_MS + (long) PwmRandom.getInstance().nextInt(3600 * 1000));
-                daemonTimer.schedule(new PublishTask(), nextPublishTime, PwmConstants.STATISTICS_PUBLISH_FREQUENCY_MS);
-            }
+            executorService = JavaHelper.makeSingleThreadExecutorService(pwmApplication, this.getClass());
+            executorService.scheduleAtFixedRate(new FlushTask(), 10 * 1000, DB_WRITE_FREQUENCY_MS, TimeUnit.MILLISECONDS);
+            final TimeDuration delayTillNextZulu = TimeDuration.fromCurrent(JavaHelper.nextZuluZeroTime());
+            executorService.scheduleAtFixedRate(new NightlyTask(), delayTillNextZulu.getTotalMilliseconds(), TimeUnit.DAYS.toMillis(1), TimeUnit.MILLISECONDS);
         }
 
         status = STATUS.OPEN;
@@ -375,9 +344,9 @@ public class StatisticsManager implements PwmService {
         } catch (Exception e) {
             LOGGER.error("unexpected error closing: " + e.getMessage());
         }
-        if (daemonTimer != null) {
-            daemonTimer.cancel();
-        }
+
+        JavaHelper.closeAndWaitExecutor(executorService, new TimeDuration(3, TimeUnit.SECONDS));
+
         status = STATUS.CLOSED;
     }
 
@@ -390,7 +359,6 @@ public class StatisticsManager implements PwmService {
         public void run() {
             writeDbValues();
             resetDailyStats();
-            daemonTimer.schedule(new NightlyTask(), Date.from(JavaHelper.nextZuluZeroTime()));
         }
     }
 
@@ -400,15 +368,7 @@ public class StatisticsManager implements PwmService {
         }
     }
 
-    private class PublishTask extends TimerTask {
-        public void run() {
-            try {
-                publishStatisticsToCloud();
-            } catch (Exception e) {
-                LOGGER.error("error publishing statistics to cloud: " + e.getMessage());
-            }
-        }
-    }
+
 
     public static class DailyKey {
         int year;
@@ -491,61 +451,6 @@ public class StatisticsManager implements PwmService {
         return epsMeterMap.get(type.toString() + duration.toString()).readEventRate();
     }
 
-    private void publishStatisticsToCloud()
-            throws URISyntaxException, IOException, PwmUnrecoverableException {
-        final StatsPublishBean statsPublishData;
-        {
-            final StatisticsBundle bundle = getStatBundleForKey(KEY_CUMULATIVE);
-            final Map<String,String> statData = new HashMap<>();
-            for (final Statistic loopStat : Statistic.values()) {
-                statData.put(loopStat.getKey(),bundle.getStatistic(loopStat));
-            }
-            final Configuration config = pwmApplication.getConfig();
-            final List<String> configuredSettings = new ArrayList<>();
-            for (final PwmSetting pwmSetting : config.nonDefaultSettings()) {
-                if (!pwmSetting.getCategory().hasProfiles() && !config.isDefaultValue(pwmSetting)) {
-                    configuredSettings.add(pwmSetting.getKey());
-                }
-            }
-            final Map<String,String> otherData = new HashMap<>();
-            otherData.put(StatsPublishBean.KEYS.SITE_URL.toString(),config.readSettingAsString(PwmSetting.PWM_SITE_URL));
-            otherData.put(StatsPublishBean.KEYS.SITE_DESCRIPTION.toString(),config.readSettingAsString(PwmSetting.PUBLISH_STATS_SITE_DESCRIPTION));
-            otherData.put(StatsPublishBean.KEYS.INSTALL_DATE.toString(), JavaHelper.toIsoDate(pwmApplication.getInstallTime()));
-
-            try {
-                otherData.put(StatsPublishBean.KEYS.LDAP_VENDOR.toString(),pwmApplication.getProxyChaiProvider(config.getDefaultLdapProfile().getIdentifier()).getDirectoryVendor().toString());
-            } catch (Exception e) {
-                LOGGER.trace("unable to read ldap vendor type for stats publication: " + e.getMessage());
-            }
-
-            statsPublishData = new StatsPublishBean(
-                    pwmApplication.getInstanceID(),
-                    Instant.now(),
-                    statData,
-                    configuredSettings,
-                    PwmConstants.BUILD_NUMBER,
-                    PwmConstants.BUILD_VERSION,
-                    otherData
-            );
-        }
-        final URI requestURI = new URI(PwmConstants.PWM_URL_CLOUD + "/rest/pwm/statistics");
-        final HttpPost httpPost = new HttpPost(requestURI.toString());
-        final String jsonDataString = JsonUtil.serialize(statsPublishData);
-        httpPost.setEntity(new StringEntity(jsonDataString));
-        httpPost.setHeader("Accept", PwmConstants.AcceptValue.json.getHeaderValue());
-        httpPost.setHeader("Content-Type", PwmConstants.ContentTypeValue.json.getHeaderValue());
-        LOGGER.debug("preparing to send anonymous statistics to " + requestURI.toString() + ", data to send: " + jsonDataString);
-        final HttpResponse httpResponse = PwmHttpClient.getHttpClient(pwmApplication.getConfig()).execute(httpPost);
-        if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
-            throw new IOException("http response error code: " + httpResponse.getStatusLine().getStatusCode());
-        }
-        LOGGER.info("published anonymous statistics to " + requestURI.toString());
-        try {
-            localDB.put(LocalDB.DB.PWM_STATS, KEY_CLOUD_PUBLISH_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
-        } catch (LocalDBException e) {
-            LOGGER.error("unexpected error trying to save last statistics published time to LocalDB: " + e.getMessage());
-        }
-    }
 
     public int outputStatsToCsv(final OutputStream outputStream, final Locale locale, final boolean includeHeader)
             throws IOException

+ 207 - 0
src/main/java/password/pwm/svc/telemetry/FtpTelemetrySender.java

@@ -0,0 +1,207 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 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.svc.telemetry;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.commons.net.ftp.FTPSClient;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.TelemetryPublishBean;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public class FtpTelemetrySender implements TelemetrySender {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(FtpTelemetrySender.class);
+
+    private Settings settings;
+
+    @Override
+    public void init(final PwmApplication pwmApplication,final  String initString) {
+        settings = JsonUtil.deserialize(initString, Settings.class);
+    }
+
+    @Override
+    public void publish(final TelemetryPublishBean telemetryPublishBean) throws PwmUnrecoverableException
+    {
+        ftpPut(telemetryPublishBean);
+    }
+
+    private void ftpPut(final TelemetryPublishBean telemetryPublishBean) throws PwmUnrecoverableException
+    {
+        final FTPClient ftpClient;
+        switch (settings.getFtpMode()) {
+            case ftp:
+                ftpClient = new FTPClient();
+                break;
+
+            case ftps:
+                ftpClient = new FTPSClient();
+                break;
+
+            default:
+                JavaHelper.unhandledSwitchStatement(settings.getFtpMode());
+                throw new UnsupportedOperationException();
+        }
+
+
+        // connect
+        try {
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "establishing " + settings.getFtpMode() + " connection to " + settings.getHost());
+            ftpClient.connect(settings.getHost());
+
+            final int reply = ftpClient.getReplyCode();
+            if (!FTPReply.isPositiveCompletion(reply)) {
+                disconnectFtpClient(ftpClient);
+                final String msg = "error " + reply + " connecting to " + settings.getHost();
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+            }
+
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "connected to " + settings.getHost());
+        } catch (IOException e) {
+            disconnectFtpClient(ftpClient);
+            final String msg = "unable to connect to " + settings.getHost() + ", error: " + e.getMessage();
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+        }
+
+        // set modes
+        try {
+            ftpClient.enterLocalPassiveMode();
+            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
+
+            final int reply = ftpClient.getReplyCode();
+            if (!FTPReply.isPositiveCompletion(reply)) {
+                disconnectFtpClient(ftpClient);
+                final String msg = "error setting file type mode to binary, error=" + reply;
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+            }
+        } catch (IOException e) {
+            disconnectFtpClient(ftpClient);
+            final String msg = "unable to connect to " + settings.getHost() + ", error: " + e.getMessage();
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+        }
+
+        // authenticate
+        try {
+            ftpClient.login(settings.getUsername(), settings.getPassword());
+
+            final int reply = ftpClient.getReplyCode();
+            if (!FTPReply.isPositiveCompletion(reply)) {
+                disconnectFtpClient(ftpClient);
+                final String msg = "error authenticating as " + settings.getUsername() + " to " + settings.getHost() + ", error=" + reply;
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+            }
+
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "authenticated to " + settings.getHost() + " as " + settings.getUsername());
+        } catch (IOException e) {
+            disconnectFtpClient(ftpClient);
+            final String msg = "error authenticating as " + settings.getUsername() + " to " + settings.getHost() + ", error: " + e.getMessage();
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+        }
+
+
+        // upload
+        try {
+            final String filePath = settings.getPath() + "/" + telemetryPublishBean.getId() + ".zip";
+            final byte[] fileBytes = dataToJsonZipFile(telemetryPublishBean);
+            final ByteArrayInputStream fileStream = new ByteArrayInputStream(fileBytes);
+
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "preparing to transfer " + fileBytes.length + " bytes to file path " + filePath);
+
+            final Instant startTime = Instant.now();
+            ftpClient.storeFile(filePath, fileStream);
+
+            final int reply = ftpClient.getReplyCode();
+            if (!FTPReply.isPositiveCompletion(reply)) {
+                disconnectFtpClient(ftpClient);
+                final String msg = "error uploading file  to " + settings.getHost() + ", error=" + reply;
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+            }
+
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "completed transfer of " + fileBytes.length + " in " + TimeDuration.compactFromCurrent(startTime));
+        } catch (IOException e) {
+            disconnectFtpClient(ftpClient);
+            final String msg = "error uploading file  to " + settings.getHost() + ", error: " +e.getMessage();
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+        }
+    }
+
+    private void disconnectFtpClient(final FTPClient ftpClient) {
+        if (ftpClient.isConnected()) {
+            try {
+                ftpClient.disconnect();
+                LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "disconnected");
+            } catch (IOException e) {
+                LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "error while disconnecting ftp client: " + e.getMessage());
+            }
+        }
+    }
+
+    @Getter
+    @AllArgsConstructor
+    private static class Settings implements Serializable {
+        private FTP_MODE ftpMode;
+        private String host;
+        private String username;
+        private String password;
+        private String path;
+
+        enum FTP_MODE {
+            ftp,
+            ftps,
+        }
+    }
+
+    private static byte[] dataToJsonZipFile(final TelemetryPublishBean telemetryPublishBean) throws IOException
+    {
+        final String jsonData = JsonUtil.serialize(telemetryPublishBean, JsonUtil.Flag.PrettyPrint);
+        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        final ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
+        final ZipEntry e = new ZipEntry(telemetryPublishBean.getId() + ".json");
+        zipOutputStream.putNextEntry(e);
+
+        final byte[] data = jsonData.getBytes(PwmConstants.DEFAULT_CHARSET);
+        zipOutputStream.write(data, 0, data.length);
+        zipOutputStream.closeEntry();
+        zipOutputStream.close();
+        return byteArrayOutputStream.toByteArray();
+    }
+
+}

+ 86 - 0
src/main/java/password/pwm/svc/telemetry/HttpTelemetrySender.java

@@ -0,0 +1,86 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 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.svc.telemetry;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.TelemetryPublishBean;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpHeader;
+import password.pwm.http.HttpMethod;
+import password.pwm.http.client.PwmHttpClient;
+import password.pwm.http.client.PwmHttpClientConfiguration;
+import password.pwm.http.client.PwmHttpClientRequest;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+public class HttpTelemetrySender implements TelemetrySender {
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass(HttpTelemetrySender.class);
+
+    private PwmApplication pwmApplication;
+    private Settings settings;
+
+    @Override
+    public void init(final PwmApplication pwmApplication, final String initString)
+    {
+        this.pwmApplication = pwmApplication;
+        settings = JsonUtil.deserialize(initString, HttpTelemetrySender.Settings.class);
+    }
+
+    @Override
+    public void publish(final TelemetryPublishBean statsPublishBean)
+            throws PwmUnrecoverableException
+    {
+        final PwmHttpClientConfiguration pwmHttpClientConfiguration = new PwmHttpClientConfiguration.Builder()
+                .setPromiscuous(true)
+                .create();
+        final PwmHttpClient pwmHttpClient = new PwmHttpClient(pwmApplication, SessionLabel.TELEMETRY_SESSION_LABEL, pwmHttpClientConfiguration);
+        final String body = JsonUtil.serialize(statsPublishBean);
+        final Map<String,String> headers = new HashMap<>();
+        headers.put(HttpHeader.Content_Type.getHttpName(), PwmConstants.ContentTypeValue.json.getHeaderValue());
+        headers.put(HttpHeader.Accept.getHttpName(), PwmConstants.AcceptValue.json.getHeaderValue());
+        final PwmHttpClientRequest pwmHttpClientRequest = new PwmHttpClientRequest(
+                HttpMethod.POST,
+                settings.getUrl(),
+                body,
+                headers
+        );
+        LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL,"preparing to send telemetry data to '" + settings.getUrl() + ")");
+        pwmHttpClient.makeRequest(pwmHttpClientRequest);
+        LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL,"sent telemetry data to '" + settings.getUrl() + ")");
+    }
+
+    @Getter
+    @AllArgsConstructor
+    private static class Settings implements Serializable {
+        private String url;
+    }
+}

+ 33 - 0
src/main/java/password/pwm/svc/telemetry/TelemetrySender.java

@@ -0,0 +1,33 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 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.svc.telemetry;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.TelemetryPublishBean;
+import password.pwm.error.PwmUnrecoverableException;
+
+public interface TelemetrySender {
+    void init(PwmApplication pwmApplication, String initString);
+
+    void publish(TelemetryPublishBean statsPublishBean) throws PwmUnrecoverableException;
+}

+ 338 - 0
src/main/java/password/pwm/svc/telemetry/TelemetryService.java

@@ -0,0 +1,338 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 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.svc.telemetry;
+
+import com.novell.ldapchai.provider.ChaiProvider;
+import lombok.Builder;
+import lombok.Getter;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmApplicationMode;
+import password.pwm.PwmConstants;
+import password.pwm.PwmEnvironment;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.TelemetryPublishBean;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.LdapProfile;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.health.HealthRecord;
+import password.pwm.svc.PwmService;
+import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsBundle;
+import password.pwm.svc.stats.StatisticsManager;
+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.localdb.LocalDB;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroMachine;
+import password.pwm.util.secure.PwmRandom;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class TelemetryService implements PwmService {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(TelemetryService.class);
+
+    private ScheduledExecutorService executorService;
+    private PwmApplication pwmApplication;
+    private Settings settings;
+
+    private Instant lastPublishTime;
+    private ErrorInformation lastError;
+    private TelemetrySender sender;
+
+    private STATUS status = STATUS.NEW;
+
+
+    @Override
+    public STATUS status()
+    {
+        return null;
+    }
+
+    @Override
+    public void init(final PwmApplication pwmApplication) throws PwmException
+    {
+        status = STATUS.OPENING;
+        this.pwmApplication = pwmApplication;
+
+        if (pwmApplication.getApplicationMode() != PwmApplicationMode.RUNNING) {
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "will remain closed, app is not running");
+            status = STATUS.CLOSED;
+            return;
+        }
+
+        if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PUBLISH_STATS_ENABLE)) {
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "will remain closed, publish stats not enabled");
+            status = STATUS.CLOSED;
+            return;
+        }
+
+        if (pwmApplication.getLocalDB().status() != LocalDB.Status.OPEN) {
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "will remain closed, localdb not enabled");
+            status = STATUS.CLOSED;
+            return;
+        }
+
+        if (pwmApplication.getStatisticsManager().status() != STATUS.OPEN) {
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "will remain closed, statistics manager is not enabled");
+            status = STATUS.CLOSED;
+            return;
+        }
+
+        settings = Settings.fromConfig(pwmApplication.getConfig());
+        try {
+            initSender();
+        } catch (PwmUnrecoverableException e) {
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "will remain closed, unable to init sender: " + e.getMessage());
+            status = STATUS.CLOSED;
+            return;
+        }
+
+        {
+            final Instant storedLastPublishTimestamp = pwmApplication.readAppAttribute(PwmApplication.AppAttribute.TELEMETRY_LAST_PUBLISH_TIMESTAMP, Instant.class);
+            lastPublishTime = storedLastPublishTimestamp != null ?
+                    storedLastPublishTimestamp :
+                    pwmApplication.getInstallTime();
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "last publish time was " + JavaHelper.toIsoDate(lastPublishTime));
+        }
+
+        executorService = JavaHelper.makeSingleThreadExecutorService(pwmApplication, TelemetryService.class);
+
+        scheduleNextJob();
+    }
+
+    private void initSender() throws PwmUnrecoverableException
+    {
+        if (StringUtil.isEmpty(settings.getSenderImplementation())) {
+            final String msg = "telemetry sender implementation not specified";
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TELEMETRY_SEND_ERROR, msg));
+        }
+
+        final TelemetrySender telemetrySender;
+        try {
+            final String senderClass = settings.getSenderImplementation();
+            final Class theClass = Class.forName(senderClass);
+            telemetrySender = (TelemetrySender) theClass.newInstance();
+        } catch (Exception e) {
+            final String msg = "unable to load implementation class: " + e.getMessage();
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, msg));
+        }
+
+        try {
+            final String macrodSettings = MacroMachine.forNonUserSpecific(pwmApplication, null).expandMacros(settings.getSenderSettings());
+            telemetrySender.init(pwmApplication, macrodSettings);
+        } catch (Exception e) {
+            final String msg = "unable to init implementation class: " + e.getMessage();
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, msg));
+        }
+        sender = telemetrySender;
+    }
+
+    private void executePublishJob() throws PwmUnrecoverableException, IOException, URISyntaxException
+    {
+        final String authValue = pwmApplication.getStatisticsManager().getStatBundleForKey(StatisticsManager.KEY_CUMULATIVE).getStatistic(Statistic.AUTHENTICATIONS);
+        if (StringUtil.isEmpty(authValue) || Integer.parseInt(authValue) < settings.getMinimumAuthentications()) {
+            LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "skipping telemetry send, authentication count is too low");
+        } else {
+            try {
+                final TelemetryPublishBean telemetryPublishBean = generatePublishableBean();
+                sender.publish(telemetryPublishBean);
+                LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "sent telemetry data: " + JsonUtil.serialize(telemetryPublishBean));
+            } catch (PwmException e) {
+                lastError = e.getErrorInformation();
+                LOGGER.error(SessionLabel.TELEMETRY_SESSION_LABEL, "error sending telemetry data: " + e.getMessage());
+            }
+        }
+
+        lastPublishTime = Instant.now();
+        pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.TELEMETRY_LAST_PUBLISH_TIMESTAMP, lastPublishTime);
+        scheduleNextJob();
+    }
+
+    private void scheduleNextJob() {
+        final TimeDuration durationUntilNextPublish = durationUntilNextPublish();
+        executorService.schedule(
+                new PublishJob(),
+                durationUntilNextPublish.getTotalMilliseconds(),
+                TimeUnit.MILLISECONDS);
+        LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "next publish time: " + durationUntilNextPublish().asCompactString());
+    }
+
+    private class PublishJob implements Runnable {
+        @Override
+        public void run()
+        {
+            try {
+                executePublishJob();
+            } catch (PwmException e) {
+                LOGGER.error(e.getErrorInformation());
+            } catch (Exception e) {
+                LOGGER.error("unexpected error during telemetry publish job: " + e.getMessage());
+            }
+        }
+    }
+
+    @Override
+    public void close()
+    {
+
+    }
+
+    @Override
+    public List<HealthRecord> healthCheck()
+    {
+        return null;
+    }
+
+    @Override
+    public ServiceInfoBean serviceInfo()
+    {
+        final Map<String,String> debugMap = new LinkedHashMap<>();
+        debugMap.put("lastPublishTime", JavaHelper.toIsoDate(lastPublishTime));
+        debugMap.put("lastError", lastError.toDebugStr());
+        return new ServiceInfoBean(null,Collections.unmodifiableMap(debugMap));
+    }
+
+
+    private TelemetryPublishBean generatePublishableBean()
+            throws URISyntaxException, IOException, PwmUnrecoverableException
+    {
+        final StatisticsBundle bundle = pwmApplication.getStatisticsManager().getStatBundleForKey(StatisticsManager.KEY_CUMULATIVE);
+        final Configuration config = pwmApplication.getConfig();
+
+        final Map<String,String> statData = new TreeMap<>();
+        for (final Statistic loopStat : Statistic.values()) {
+            statData.put(loopStat.getKey(),bundle.getStatistic(loopStat));
+        }
+
+        final List<String> configuredSettings = new ArrayList<>();
+        for (final PwmSetting pwmSetting : config.nonDefaultSettings()) {
+            if (!pwmSetting.getCategory().hasProfiles() && !config.isDefaultValue(pwmSetting)) {
+                configuredSettings.add(pwmSetting.getKey());
+            }
+        }
+
+        final Set<ChaiProvider.DIRECTORY_VENDOR> ldapVendors = new LinkedHashSet<>();
+        for (final LdapProfile ldapProfile : config.getLdapProfiles().values()) {
+            try {
+                ldapVendors.add(ldapProfile.getProxyChaiProvider(pwmApplication).getDirectoryVendor());
+            } catch (Exception e) {
+                LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "unable to read ldap vendor type for stats publication: " + e.getMessage());
+            }
+        }
+
+        final TelemetryPublishBean.TelemetryPublishBeanBuilder builder = TelemetryPublishBean.builder();
+        builder.timestamp(Instant.now());
+        builder.id(makeId(pwmApplication));
+        builder.instanceID(pwmApplication.getInstanceID());
+        builder.installTime(pwmApplication.getInstallTime());
+        builder.siteDescription(config.readSettingAsString(PwmSetting.PUBLISH_STATS_SITE_DESCRIPTION));
+        builder.versionBuild(PwmConstants.BUILD_NUMBER);
+        builder.versionVersion(PwmConstants.BUILD_VERSION);
+        builder.ldapVendor(Collections.unmodifiableList(new ArrayList<>(ldapVendors)));
+        builder.statistics(Collections.unmodifiableMap(statData));
+        builder.configuredSettings(Collections.unmodifiableList(configuredSettings));
+
+        final TelemetryPublishBean.Environment environment = TelemetryPublishBean.Environment.builder()
+                .appliance(pwmApplication.getPwmEnvironment().getFlags().contains(PwmEnvironment.ApplicationFlag.Appliance))
+                .javaVendor(System.getProperty("java.vm.vendor"))
+                .javaName(System.getProperty("java.vm.name"))
+                .javaVersion(System.getProperty("java.vm.version"))
+                .osName(System.getProperty("os.name"))
+                .osVersion(System.getProperty("os.version"))
+                .build();
+        builder.environment(environment);
+
+        return builder.build();
+    }
+
+    private static String makeId(final PwmApplication pwmApplication) throws PwmUnrecoverableException
+    {
+        final String SEPARATOR = "-";
+        final String DATETIME_PATTERN = "yyyyMMdd-HHmmss'Z'";
+        final String timestamp = DateTimeFormatter.ofPattern(DATETIME_PATTERN).format(ZonedDateTime.now(ZoneId.of("Zulu")));
+        return PwmConstants.PWM_APP_NAME.toLowerCase()
+                + SEPARATOR + instanceHash(pwmApplication)
+                + SEPARATOR + timestamp;
+
+    }
+
+    private static String instanceHash(final PwmApplication pwmApplication) throws PwmUnrecoverableException
+    {
+        final int MAX_HASH_LENGTH = 64;
+        final String instanceID = pwmApplication.getInstanceID();
+        final String hash = pwmApplication.getSecureService().hash(instanceID);
+        return hash.length() > 64
+                ? hash.substring(0, MAX_HASH_LENGTH)
+                : hash;
+    }
+
+    @Getter
+    @Builder
+    private static class Settings {
+        private TimeDuration publishFrequency;
+        private int minimumAuthentications;
+        private String senderImplementation;
+        private String senderSettings;
+
+        static Settings fromConfig(final Configuration config) {
+            return Settings.builder()
+                    .minimumAuthentications(Integer.parseInt(config.readAppProperty(AppProperty.TELEMETRY_MIN_AUTHENTICATIONS)))
+                    .publishFrequency(new TimeDuration(Integer.parseInt(config.readAppProperty(AppProperty.TELEMETRY_SEND_FREQUENCY_SECONDS)),TimeUnit.SECONDS))
+                    .senderImplementation(config.readAppProperty(AppProperty.TELEMETRY_SENDER_IMPLEMENTATION))
+                    .senderSettings(config.readAppProperty(AppProperty.TELEMETRY_SENDER_SETTINGS))
+                    .build();
+        }
+    }
+
+    private TimeDuration durationUntilNextPublish() {
+
+        final Instant nextPublishTime = settings.getPublishFrequency().incrementFromInstant(lastPublishTime);
+        final Instant minuteFromNow = TimeDuration.MINUTE.incrementFromInstant(Instant.now());
+        return nextPublishTime.isBefore(minuteFromNow)
+                ? TimeDuration.fromCurrent(minuteFromNow)
+                : TimeDuration.fromCurrent(nextPublishTime.toEpochMilli() + (PwmRandom.getInstance().nextInt(600) - 300));
+    }
+
+}

+ 3 - 2
src/main/java/password/pwm/util/VersionChecker.java → src/main/java/password/pwm/svc/telemetry/VersionChecker.java

@@ -20,7 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.util;
+package password.pwm.svc.telemetry;
 
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
@@ -151,7 +151,7 @@ public class VersionChecker implements PwmService {
         if (PwmConstants.BUILD_NUMBER == null || PwmConstants.BUILD_NUMBER.length() < 1) {
             return true;
         }
-
+        /*
         try {
             final VersionCheckInfoCache versionCheckInfo = getVersionCheckInfo();
             final String currentBuild = versionCheckInfo.getCurrentBuild();
@@ -169,6 +169,7 @@ public class VersionChecker implements PwmService {
         } catch (Exception e) {
             LOGGER.error("unable to retrieve current version data from cloud: " + e.toString());
         }
+        */
         return true;
     }
 

+ 1 - 1
src/main/java/password/pwm/svc/token/TokenService.java

@@ -249,7 +249,7 @@ public class TokenService implements PwmService {
             throw new PwmOperationalException(errorInformation);
         }
 
-        LOGGER.trace(sessionLabel, "generated toke with payload: "  + tokenPayload.toDebugString());
+        LOGGER.trace(sessionLabel, "generated token with payload: "  + tokenPayload.toDebugString());
 
         final AuditRecord auditRecord = new AuditRecordFactory(pwmApplication).createUserAuditRecord(
                 AuditEvent.TOKEN_ISSUED,

+ 12 - 0
src/main/java/password/pwm/util/java/JavaHelper.java

@@ -26,8 +26,10 @@ import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.io.IOUtils;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.http.ContextManager;
 import password.pwm.util.logging.PwmLogger;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -433,4 +435,14 @@ public class JavaHelper {
         sb.append('\n');
         return sb.toString();
     }
+
+    public static String readEulaText(final ContextManager contextManager, final String filename)
+            throws IOException
+    {
+        final String path = PwmConstants.URL_PREFIX_PUBLIC + "/resources/text/" + filename;
+        final InputStream inputStream = contextManager.getResourceAsStream(path);
+        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        copyWhilePredicate(inputStream, byteArrayOutputStream, o -> true);
+        return byteArrayOutputStream.toString(PwmConstants.DEFAULT_CHARSET.name());
+    }
 }

+ 6 - 0
src/main/java/password/pwm/util/java/TimeDuration.java

@@ -182,6 +182,12 @@ public class TimeDuration implements Comparable, Serializable {
         return new TimeDuration(this.getTotalMilliseconds() + duration.getTotalMilliseconds());
     }
 
+    public Instant incrementFromInstant(final Instant input) {
+        final long inputMillis = input.toEpochMilli();
+        final long nextMills = inputMillis + this.getTotalMilliseconds();
+        return Instant.ofEpochMilli(nextMills);
+    }
+
     public long getTotalMilliseconds() {
         return ms;
     }

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

@@ -264,6 +264,10 @@ security.defaultEphemeralHashAlg=SHA512
 security.config.minSecurityKeyLength=32
 seedlist.builtin.path=/WEB-INF/seedlist.zip
 smtp.subjectEncodingCharset=UTF8
+telemetry.senderImplementation=password.pwm.svc.telemetry.HttpTelemetrySender
+telemetry.senderSettings={"url":"https://www.pwm-project.org/pwm-data-service/telemetry"}
+telemetry.sendFrequencySeconds=259203
+telemetry.minimumAuthentications=10
 token.maxUniqueCreateAttempts=100
 token.resend.enabled=true
 token.resend.delayMS=3000

+ 12 - 5
src/main/resources/password/pwm/config/PwmSetting.xml

@@ -66,7 +66,7 @@
         <example>https://www.example.com/example</example>
         <default/>
     </setting>
-    <setting hidden="false" key="pwm.versionCheck.enable" level="1" required="true">
+    <setting hidden="true" key="pwm.versionCheck.enable" level="1" required="true">
         <default>
             <value>false</value>
         </default>
@@ -2569,9 +2569,7 @@
     </setting>
     <setting hidden="false" key="display.newuser.agreement" level="1">
         <flag>MacroSupport</flag>
-        <default>
-            <value />
-        </default>
+        <default/>
     </setting>
     <setting hidden="false" key="newUser.form" level="1">
         <ldapPermission actor="proxy" access="write"/>
@@ -2639,8 +2637,11 @@
         </properties>
     </setting>
     <setting hidden="false" key="newUser.profile.displayName" level="1">
+        <default/>
+    </setting>
+    <setting hidden="false" key="newUser.profile.visible" level="1">
         <default>
-            <value></value>
+            <value>true</value>
         </default>
     </setting>
     <setting hidden="false" key="newUser.redirectUrl" level="1">
@@ -3101,6 +3102,12 @@
             <value>directReports</value>
         </default>
     </setting>
+    <setting hidden="false" key="peopleSearch.orgChart.assistantAttribute" level="1">
+        <ldapPermission actor="self_other" access="read"/>
+        <default>
+            <value>assistant</value>
+        </default>
+    </setting>
     <setting hidden="false" key="ldap.edirectory.storeNmasResponses" level="1" required="true">
         <default>
             <value>false</value>

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

@@ -33,6 +33,8 @@ ldap_server_title=LDAP Server
 ldap_server_title_hostname=Hostname
 ldap_server_title_port=Port
 ldap_server_title_secure=Secure (TLS) Connection
+ldap_telemetry_enable_title=Enable Sharing of Statistical Data and Feature Usage
+ldap_telemetry_description_title=Enable
 ldap_testuser_description= <p>@PwmAppName@ can periodically check the connection to your LDAP directory.  To perform these checks, @PwmAppName@ needs a test user account configured.  This user account should be created amongst typical user accounts in the LDAP directory.</p><p>@PwmAppName@ will modify the password of the test user account and perform other operations to verify the configuration and the directory's health.  Many configuration settings can also be validated during this process.</p><p>This setting is optional but recommended.  If you do not wish to configure a test user at this time, you can leave this setting blank for now and configure it later.</p>
 password_description=To protect this system, you will need to set a configuration password.  The configuration password will be required whenever you wish to modify the configuration.
 password_title=Configuration Password

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

@@ -472,6 +472,7 @@ Setting_Description_newUser.minimumWaitTime=Specify a delay time during a new us
 Setting_Description_newUser.passwordPolicy.user=Specify the user @PwmAppName@ uses a template for the new user password policy. If the value is <i>TESTUSER</i>, @PwmAppName@ uses the configured test user's password policy.
 Setting_Description_newUser.profile.displayName=Specify the publicly viewable display name of this profile.
 Setting_Description_newUser.profile.list=List of New User profiles. When you configure multiple new user profiles, the user can select which profile to complete.  @PwmAppName@ shows the profile name to the users as the value of the setting <code>@PwmSettingReference\:newUser.profile.displayName@</code>.
+Setting_Description_newUser.profile.visible=Show this New User profile to users when they select New User registration.  If disabled, this profile is still available by direct URL but is not shown as a selectable profile. 
 Setting_Description_newUser.promptForPassword=Prompt user for password during user registration.  If not enabled, a random password will be assigned to the user.  In most cases you will want this enabled.
 Setting_Description_newUser.redirectUrl=URL to redirect user to after new user registration process is completed.
 Setting_Description_newUser.sms.verification=Enable this option to have @PwmAppName@ send an SMS to the new user's mobile phone number before it creates the account. The NewUser must verify receipt of the SMS before @PwmAppName@ creates the account.
@@ -576,8 +577,8 @@ Setting_Description_pwm.forwardURL=Specify a URL that @PwmAppName@ forwards user
 Setting_Description_pwm.homeURL=Specify the URL to redirect the user to upon clicking the home button. If blank, the home button returns the user to the application context URL.
 Setting_Description_pwmInstanceName=Specify the name of this application instance. If blank, @PwmAppName@ uses a persistent, randomly generated value. The recommended value is blank.
 Setting_Description_pwm.logoutURL=Specify the URL to redirect user to upon logout. If users access the site through a web authentication gateway, set the Logout URL to the gateway's Logout URL. If you are using a gateway and do not include the proper logout URL here, then users encounter authentication errors, intruder lockouts, and other problems. If things are working properly then the users see the gateway log out screen when logging out.<br/><br/>You can set the Logout URL to any appropriate relative or absolute URL.  At the time the user's browser requests this URL, the local session has already been invalidated.<br/><br/>You can always override this setting for any given user session by adding a <b>logoutURL</b> parameter to any HTTP request during the session.
-Setting_Description_pwm.publishStats.enable=Enable this option to periodically publish the statistics of this application to the project website. The published statistics are\:<ul><li>Instance Name</li><li>Version/Build Information</li><li>Cumulative Statistics</li><li>Which settings are non-default (but not the actual setting values)</li></ul>Leaving this feature enabled helps developers know which features the administrators use.
-Setting_Description_pwm.publishStats.siteDescription=Specify this optional site description if you enabled Anonymous statistics publishing. @PwmAppName@ publishes this site description along with the otherwise anonymous statistics.  This can be an organization name or other similar value.
+Setting_Description_pwm.publishStats.enable=Enable this option to periodically share anonymous statistics of @PwmAppName@. The published statistics are\:<ul><li>Version/Build Information</li><li>Cumulative Statistics</li><li>Which settings are non-default (but not the actual setting values)</li><li>Operating system name and version</li></ul>Enabling this setting helps @PwmAppName@ developers know which features are used most often.
+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>
@@ -946,6 +947,7 @@ Setting_Label_newUser.minimumWaitTime=New User Minimum Wait Time
 Setting_Label_newUser.passwordPolicy.user=Password Policy Template
 Setting_Label_newUser.profile.displayName=Profile Display Name
 Setting_Label_newUser.profile.list=New User Profile
+Setting_Label_newUser.profile.visible=Profile Visible on Menu
 Setting_Label_newUser.promptForPassword=Prompt User for Password
 Setting_Label_newUser.redirectUrl=After Registration Redirect URL
 Setting_Label_newUser.sms.verification=Enable New User SMS Verification

+ 5 - 4
src/main/webapp/WEB-INF/jsp/configguide-app.jsp

@@ -1,5 +1,6 @@
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -47,12 +48,12 @@
                     Example: <code><%=PwmSetting.PWM_SITE_URL.getExample(ConfigGuideForm.generateStoredConfig(configGuideBean).getTemplateSet())%></code>
                     <br/><br/>
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_APP_SITEURL%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_APP_SITEURL%>" style="padding-left: 5px; padding-top: 5px">
                             <label>
                                 <b>Site URL</b>
                                 <br/>
-                                <input class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_APP_SITEURL%>" name="<%=ConfigGuideForm.FormParameter.PARAM_APP_SITEURL%>"
-                                       value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_APP_SITEURL))%>" required autofocus
+                                <input class="configStringInput" id="<%=ConfigGuideFormField.PARAM_APP_SITEURL%>" name="<%=ConfigGuideFormField.PARAM_APP_SITEURL%>"
+                                       value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_APP_SITEURL))%>" required autofocus
                                        pattern="<%=PwmSetting.PWM_SITE_URL.getRegExPattern()%>"/>
                             </label>
                         </div>
@@ -83,7 +84,7 @@
         });
 
         function checkIfNextEnabled() {
-            var siteUrlInput = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_APP_SITEURL%>');
+            var siteUrlInput = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_APP_SITEURL%>');
             var passed = siteUrlInput.value.length > 1 && new RegExp(siteUrlInput.getAttribute('pattern')).test(siteUrlInput.value);
             if (passed) {
                 PWM_MAIN.getObject('button_next').disabled = false;

+ 13 - 12
src/main/webapp/WEB-INF/jsp/configguide-database.jsp

@@ -2,6 +2,7 @@
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="java.util.Locale" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -46,7 +47,7 @@
                 </div>
                 <div class="setting_body">
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_DB_CLASSNAME%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_DB_CLASSNAME%>" style="padding-left: 5px; padding-top: 5px">
                             <%=PwmSetting.DATABASE_JDBC_DRIVER.getDescription(userLocale)%>
                             <br/>
                             <% if (configGuideBean.getDatabaseDriver() != null && !configGuideBean.getDatabaseDriver().toInfoMap().isEmpty()) { %>
@@ -86,11 +87,11 @@
                 </div>
                 <div class="setting_body">
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_DB_CLASSNAME%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_DB_CLASSNAME%>" style="padding-left: 5px; padding-top: 5px">
                             <label>
                                 <%=PwmSetting.DATABASE_CLASS.getDescription(userLocale)%>
                                 <br/>
-                                <input class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_DB_CLASSNAME%>" name="<%=ConfigGuideForm.FormParameter.PARAM_DB_CLASSNAME%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_DB_CLASSNAME))%>" required autofocus/>
+                                <input class="configStringInput" id="<%=ConfigGuideFormField.PARAM_DB_CLASSNAME%>" name="<%=ConfigGuideFormField.PARAM_DB_CLASSNAME%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_DB_CLASSNAME))%>" required autofocus/>
                             </label>
                         </div>
                     </div>
@@ -103,11 +104,11 @@
                 </div>
                 <div class="setting_body">
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_DB_CONNECT_URL%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_DB_CONNECT_URL%>" style="padding-left: 5px; padding-top: 5px">
                             <label>
                                 <%=PwmSetting.DATABASE_URL.getDescription(userLocale)%>
                                 <br/>
-                                <input class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_DB_CONNECT_URL%>" name="<%=ConfigGuideForm.FormParameter.PARAM_DB_CONNECT_URL%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_DB_CONNECT_URL))%>" required autofocus/>
+                                <input class="configStringInput" id="<%=ConfigGuideFormField.PARAM_DB_CONNECT_URL%>" name="<%=ConfigGuideFormField.PARAM_DB_CONNECT_URL%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_DB_CONNECT_URL))%>" required autofocus/>
                             </label>
                         </div>
                     </div>
@@ -121,11 +122,11 @@
                 </div>
                 <div class="setting_body">
                 <div class="setting_item">
-                    <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_DB_USERNAME%>" style="padding-left: 5px; padding-top: 5px">
+                    <div id="titlePane_<%=ConfigGuideFormField.PARAM_DB_USERNAME%>" style="padding-left: 5px; padding-top: 5px">
                         <label>
                             <%=PwmSetting.DATABASE_USERNAME.getDescription(userLocale)%>
                             <br/>
-                            <input class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_DB_USERNAME%>" name="<%=ConfigGuideForm.FormParameter.PARAM_DB_USERNAME%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_DB_USERNAME))%>" required autofocus/>
+                            <input class="configStringInput" id="<%=ConfigGuideFormField.PARAM_DB_USERNAME%>" name="<%=ConfigGuideFormField.PARAM_DB_USERNAME%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_DB_USERNAME))%>" required autofocus/>
                         </label>
                     </div>
                 </div>
@@ -139,11 +140,11 @@
 
                 <div class="setting_body">
                 <div class="setting_item">
-                    <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_DB_PASSWORD%>" style="padding-left: 5px; padding-top: 5px">
+                    <div id="titlePane_<%=ConfigGuideFormField.PARAM_DB_PASSWORD%>" style="padding-left: 5px; padding-top: 5px">
                         <label>
                             <%=PwmSetting.DATABASE_PASSWORD.getDescription(userLocale)%>
                             <br/>
-                            <input style="width:200px" type="password" class="configStringInput passwordfield" id="<%=ConfigGuideForm.FormParameter.PARAM_DB_PASSWORD%>" name="<%=ConfigGuideForm.FormParameter.PARAM_DB_PASSWORD%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_DB_PASSWORD))%>" required autofocus/>
+                            <input style="width:200px" type="password" class="configStringInput passwordfield" id="<%=ConfigGuideFormField.PARAM_DB_PASSWORD%>" name="<%=ConfigGuideFormField.PARAM_DB_PASSWORD%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_DB_PASSWORD))%>" required autofocus/>
                         </label>
                     </div>
                 </div>
@@ -157,14 +158,14 @@
 
                 <div class="setting_body">
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_DB_VENDOR%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_DB_VENDOR%>" style="padding-left: 5px; padding-top: 5px">
                             <label>
                                 <%=PwmSetting.DB_VENDOR_TEMPLATE.getDescription(userLocale)%>
                                 <br/>
 
-                                <% final String selectedTemplate = configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_DB_VENDOR); %>
+                                <% final String selectedTemplate = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_DB_VENDOR); %>
 
-                                <select id="<%=ConfigGuideForm.FormParameter.PARAM_DB_VENDOR%>" name="<%=ConfigGuideForm.FormParameter.PARAM_DB_VENDOR%>">
+                                <select id="<%=ConfigGuideFormField.PARAM_DB_VENDOR%>" name="<%=ConfigGuideFormField.PARAM_DB_VENDOR%>">
                                     <% if (selectedTemplate == null || selectedTemplate.isEmpty()) { %>
                                     <option value="NOTSELECTED" selected disabled> -- Please select a template -- </option>
                                     <% } %>

+ 11 - 10
src/main/webapp/WEB-INF/jsp/configguide-end.jsp

@@ -1,6 +1,7 @@
 <%@ page import="password.pwm.http.servlet.PwmServletDefinition" %>
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -47,35 +48,35 @@
                         <td><b>LDAP Template</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(PwmSetting.TEMPLATE_LDAP.getOptions().get(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP)))%>
+                            <%=StringUtil.escapeHtml(PwmSetting.TEMPLATE_LDAP.getOptions().get(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_TEMPLATE_LDAP)))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>Site URL</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_APP_SITEURL))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_APP_SITEURL))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>LDAP Server Hostname</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_HOST))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_HOST))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>LDAP Port</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_PORT))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_PORT))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>Secure (SSL) Connection</b>
                         </td>
                         <td>
-                            <%if (Boolean.parseBoolean(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE))) {%>
+                            <%if (Boolean.parseBoolean(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_SECURE))) {%>
                             <pwm:display key="Value_True"/>
                             <% } else { %>
                             <pwm:display key="Value_False"/>
@@ -86,7 +87,7 @@
                         <td><b>Proxy LDAP DN</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_PROXY_DN))%>
                         </td>
                     </tr>
                     <tr>
@@ -94,28 +95,28 @@
                         </td>
                         <td>
                             <%=StringUtil.escapeHtml(
-                                    configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_CONTEXT))%>
+                                    configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_CONTEXT))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>Administrator Group DN</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_ADMIN_GROUP))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>LDAP Test User DN</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_TEST_USER))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>Response Storage Preference</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(PwmSetting.TEMPLATE_STORAGE.getOptions().get(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_TEMPLATE_STORAGE)))%>
+                            <%=StringUtil.escapeHtml(PwmSetting.TEMPLATE_STORAGE.getOptions().get(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_TEMPLATE_STORAGE)))%>
                         </td>
                     </tr>
                 </table>

+ 97 - 0
src/main/webapp/WEB-INF/jsp/configguide-eula.jsp

@@ -0,0 +1,97 @@
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
+<%@ page import="password.pwm.http.tag.value.PwmValue" %>
+<%@ page import="com.novell.ldapchai.util.StringHelper" %>
+<%@ page import="password.pwm.util.java.StringUtil" %>
+<%@ page import="password.pwm.util.java.JavaHelper" %>
+<%--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2017 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
+  --%>
+
+<% JspUtility.setFlag(pageContext, PwmRequestFlag.HIDE_LOCALE); %>
+<% JspUtility.setFlag(pageContext, PwmRequestFlag.INCLUDE_CONFIG_CSS); %>
+<% final ConfigGuideBean configGuideBean = JspUtility.getSessionBean(pageContext, ConfigGuideBean.class);%>
+<!DOCTYPE html>
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
+<%@ taglib uri="pwm" prefix="pwm" %>
+<html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
+<%@ include file="fragment/header.jsp" %>
+<body class="nihilo">
+<div id="wrapper">
+    <%@ include file="fragment/configguide-header.jsp"%>
+    <div id="centerbody">
+        <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
+        <br/>
+        <div id="password" class="setting_outline">
+            <div class="setting_title">
+                End User License Agreement
+            </div>
+            <div class="setting_body">
+                <div id="agreementText" class="eulaText"><%=JavaHelper.readEulaText(ContextManager.getContextManager(session),PwmConstants.RESOURCE_FILE_EULA_TXT)%></div>
+            </div>
+
+            <br/><br/>
+            <div style="text-align: center">
+                <form id="configForm">
+
+                    <label class="checkboxWrapper">
+                        <input type="checkbox" id="agreeCheckBox"/>
+                        <pwm:display key="Button_Agree"/>
+                    </label>
+                </form>
+            </div>
+
+        </div>
+        <br/>
+        <%@ include file="fragment/configguide-buttonbar.jsp" %>
+    </div>
+    <div class="push"></div>
+</div>
+<pwm:script>
+    <script type="text/javascript">
+        function handleFormActivity() {
+            PWM_GUIDE.updateForm();
+            PWM_MAIN.getObject('button_next').disabled = !checkIfNextEnabled();
+        }
+
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            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,click',function(){handleFormActivity()});
+
+            handleFormActivity();
+        });
+
+        function checkIfNextEnabled() {
+
+            var checkBox = PWM_MAIN.getObject("agreeCheckBox");
+            if (checkBox != null) {
+                if (checkBox.checked) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    </script>
+</pwm:script>
+<%@ include file="fragment/footer.jsp" %>
+</body>
+</html>

+ 3 - 2
src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp

@@ -2,6 +2,7 @@
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.config.PwmSettingTemplate" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -50,7 +51,7 @@
                         <br/><br/>
                         <b>Administrator Group DN</b>
                         <br/>
-                        <input style="width:400px;" class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_ADMIN_GROUP%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_ADMIN_GROUP%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_ADMIN_GROUP))%>" <pwm:autofocus/> required/>
+                        <input style="width:400px;" class="configStringInput" id="<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>" name="<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP))%>" <pwm:autofocus/> required/>
                         <button type="button" class="btn" id="button-browse-adminGroup">
                             <span class="btn-icon pwm-icon pwm-icon-sitemap"></span>
                             <pwm:display key="Button_Browse"/>
@@ -114,7 +115,7 @@
 
             PWM_MAIN.addEventHandler('button-browse-adminGroup','click',function(){
                 UILibrary.editLdapDN(function(value){
-                    PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_LDAP_ADMIN_GROUP%>').value = value;
+                    PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>').value = value;
                     handleFormActivity();
                 })
             });

+ 4 - 3
src/main/webapp/WEB-INF/jsp/configguide-ldap_cert.jsp

@@ -7,6 +7,7 @@
 <%@ page import="password.pwm.util.secure.X509Utils" %>
 <%@ page import="java.io.ByteArrayInputStream" %>
 <%@ page import="java.security.cert.X509Certificate" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -54,10 +55,10 @@
                     LDAP Server Certificates
                 </div>
                 <div class="setting_body">
-                    <% final String serverInfo = configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_HOST) + ":" + configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_PORT); %>
+                    <% final String serverInfo = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_HOST) + ":" + configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_PORT); %>
                     <pwm:display key="ldap_cert_description" bundle="ConfigGuide" value1="<%=serverInfo%>"/>
                     <div>
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_HOST%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_LDAP_HOST%>" style="padding-left: 5px; padding-top: 5px">
                             <% int counter=0;for (final X509Certificate certificate : configGuideBean.getLdapCertificates()) {%>
                             <% final String md5sum = SecureEngine.hash(new ByteArrayInputStream(certificate.getEncoded()), PwmHashAlgorithm.MD5); %>
                             <% final String sha1sum = SecureEngine.hash(new ByteArrayInputStream(certificate.getEncoded()), PwmHashAlgorithm.SHA1); %>
@@ -106,7 +107,7 @@
                         At least one of the following options must be selected to continue.
                     </div>
                     <br/>
-                    <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN%>" style="padding-left: 5px; padding-top: 5px">
+                    <div id="titlePane_<%=ConfigGuideFormField.PARAM_LDAP_PROXY_DN%>" style="padding-left: 5px; padding-top: 5px">
                         Certificate(s) are trusted by default Java keystore
                         <br/>
                         <label class="checkboxWrapper">

+ 4 - 3
src/main/webapp/WEB-INF/jsp/configguide-ldap_context.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.tag.conditional.PwmIfTest" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -48,7 +49,7 @@
                     <div class="setting_item">
                         <b>User Container DN</b>
                         <br/>
-                        <input style="width:400px" class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_CONTEXT%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_CONTEXT%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_CONTEXT))%>" required autofocus/>
+                        <input style="width:400px" class="configStringInput" id="<%=ConfigGuideFormField.PARAM_LDAP_CONTEXT%>" name="<%=ConfigGuideFormField.PARAM_LDAP_CONTEXT%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_CONTEXT))%>" required autofocus/>
                         <button type="button" class="btn" id="button-browse-context">
                             <span class="btn-icon pwm-icon pwm-icon-sitemap"></span>
                             <pwm:display key="Button_Browse"/>
@@ -96,13 +97,13 @@
 
             PWM_MAIN.addEventHandler('button-browse-context','click',function(){
                 UILibrary.editLdapDN(function(value){
-                    PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_LDAP_CONTEXT%>').value = value;
+                    PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_CONTEXT%>').value = value;
                     handleFormActivity();
                 })
             });
             PWM_MAIN.addEventHandler('button-browse-adminGroup','click',function(){
                 UILibrary.editLdapDN(function(value){
-                    PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_LDAP_ADMIN_GROUP%>').value = value;
+                    PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>').value = value;
                     handleFormActivity();
                 })
             });

+ 4 - 3
src/main/webapp/WEB-INF/jsp/configguide-ldap_proxy.jsp

@@ -1,5 +1,6 @@
 <%@ page import="password.pwm.config.PwmSettingTemplate" %>
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -49,7 +50,7 @@
                             <br/><br/>
                             <b><pwm:display key="ldap_admin_title_proxy-dn" bundle="ConfigGuide"/></b>
                             <br/>
-                            <input class="configStringInput" type="text" style="width:400px" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN%>" value="<%=configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN)%>" <pwm:autofocus/> />
+                            <input class="configStringInput" type="text" style="width:400px" id="<%=ConfigGuideFormField.PARAM_LDAP_PROXY_DN%>" name="<%=ConfigGuideFormField.PARAM_LDAP_PROXY_DN%>" value="<%=configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_PROXY_DN)%>" <pwm:autofocus/> />
                             <% if (!isAD) { %>
                             <button type="button" class="btn" id="button-browse-adminDN">
                                 <span class="btn-icon pwm-icon pwm-icon-sitemap"></span>
@@ -63,7 +64,7 @@
                         <label>
                             <b><pwm:display key="ldap_admin_title_proxy-pw" bundle="ConfigGuide"/></b>
                             <br/>
-                            <input style="width:200px" class="configStringInput passwordfield" type="password" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_PW%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_PW%>" value="<%=configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_PW)%>"/>
+                            <input style="width:200px" class="configStringInput passwordfield" type="password" id="<%=ConfigGuideFormField.PARAM_LDAP_PROXY_PW%>" name="<%=ConfigGuideFormField.PARAM_LDAP_PROXY_PW%>" value="<%=configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_PROXY_PW)%>"/>
                         </label>
                     </div>
                 </div>
@@ -109,7 +110,7 @@
 
             PWM_MAIN.addEventHandler('button-browse-adminDN','click',function(){
                 UILibrary.editLdapDN(function(value){
-                    PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PROXY_DN%>').value = value;
+                    PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_PROXY_DN%>').value = value;
                     handleFormActivity();
                 })
             });

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

@@ -6,6 +6,7 @@
 <%@ page import="password.pwm.ldap.schema.SchemaExtender" %>
 <%@ page import="password.pwm.ldap.schema.SchemaDefinition" %>
 <%@ page import="java.util.List" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -66,7 +67,7 @@
                         <pwm:display key="Display_ConfigGuideLdapSchema" bundle="Config"/>
                     </p>
                     <div>
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_HOST%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_LDAP_HOST%>" style="padding-left: 5px; padding-top: 5px">
                             <table style="width:100%">
                                 <tr><td>Schema Detail</td></tr>
                                 <tr><td class="setting_table_value"><pre><%=schemaActivityLog%></pre></td></tr>
@@ -99,7 +100,7 @@
                 LDAP Schema
             </div>
             <div class="setting_body">
-                <% final String ldapTemplateName = PwmSetting.TEMPLATE_LDAP.getOptions().get(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP)); %>
+                <% final String ldapTemplateName = PwmSetting.TEMPLATE_LDAP.getOptions().get(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_TEMPLATE_LDAP)); %>
                 <p>The storage location is set to <i>LDAP</i>, and the LDAP directory setting template is <i><%=ldapTemplateName%></i>.</p>
                 <p>This configuration expects the LDAP server's schema to be extended or you can adjust the configuration to use pre-existing defined attributes in your LDAP directory.</p>
                 <p>LDIF files to process the schema extension are included for several directory types.</p>

+ 14 - 13
src/main/webapp/WEB-INF/jsp/configguide-ldap_server.jsp

@@ -1,5 +1,6 @@
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -48,38 +49,38 @@
                     <table class="noborder" style="border-spacing: 0; padding: 0; margin: 0">
                         <tr>
                             <td colspan="2">
-                                <label for="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_HOST%>">
+                                <label for="<%=ConfigGuideFormField.PARAM_LDAP_HOST%>">
                                 <b><pwm:display key="ldap_server_title_hostname" bundle="ConfigGuide"/></b>
                                 </label>
                             </td>
                         </tr>
                         <tr>
                             <td colspan="2">
-                                <input class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_HOST%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_HOST%>" value="<%=configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_HOST)%>" <pwm:autofocus/> />
+                                <input class="configStringInput" id="<%=ConfigGuideFormField.PARAM_LDAP_HOST%>" name="<%=ConfigGuideFormField.PARAM_LDAP_HOST%>" value="<%=configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_HOST)%>" <pwm:autofocus/> />
                             </td>
                         </tr>
                         <tr><td>&nbsp;</td></tr>
                         <tr>
                             <td style="width: 30%">
-                                <label for="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PORT%>">
+                                <label for="<%=ConfigGuideFormField.PARAM_LDAP_PORT%>">
                                     <b><pwm:display key="ldap_server_title_port" bundle="ConfigGuide"/></b>
                                 </label>
                             </td>
                             <td style="">
-                                <label for="widget_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>">
+                                <label for="widget_<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>">
                                     <b><pwm:display key="ldap_server_title_secure" bundle="ConfigGuide"/></b>
                                 </label>
                             </td>
                         </tr>
                         <tr>
                             <td>
-                                <input class="configNumericInput" type="number" min="0" max="65535" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PORT%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_PORT%>" value="<%=configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_PORT)%>"/>
+                                <input class="configNumericInput" type="number" min="0" max="65535" id="<%=ConfigGuideFormField.PARAM_LDAP_PORT%>" name="<%=ConfigGuideFormField.PARAM_LDAP_PORT%>" value="<%=configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_PORT)%>"/>
                             </td>
                             <td>
-                                <% final boolean secureChecked = "true".equalsIgnoreCase(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE));%>
+                                <% final boolean secureChecked = "true".equalsIgnoreCase(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_SECURE));%>
                                 <label class="checkboxWrapper">
-                                    <input type="checkbox" id="widget_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>" name="nope" <%=secureChecked ? "checked" : ""%>/> Secure
-                                    <input type="hidden" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>" value="uninitialized"/>
+                                    <input type="checkbox" id="widget_<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>" name="nope" <%=secureChecked ? "checked" : ""%>/> Secure
+                                    <input type="hidden" id="<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>" name="<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>" value="uninitialized"/>
                                 </label>
                             </td>
                         </tr>
@@ -103,8 +104,8 @@
 <pwm:script>
     <script type="text/javascript">
         function handleFormActivity() {
-            PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>').value =
-                    PWM_MAIN.getObject('widget_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>').checked ? "true" : "false";
+            PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>').value =
+                    PWM_MAIN.getObject('widget_<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>').checked ? "true" : "false";
             PWM_GUIDE.updateForm();
             clearHealthDiv();
         }
@@ -122,12 +123,12 @@
                 handleFormActivity();
             });
 
-            PWM_MAIN.addEventHandler('widget_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>','change',function() {
-                if (!PWM_MAIN.getObject('widget_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>').checked) {
+            PWM_MAIN.addEventHandler('widget_<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>','change',function() {
+                if (!PWM_MAIN.getObject('widget_<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>').checked) {
                     PWM_MAIN.showConfirmDialog({
                         text: PWM_CONFIG.showString('Confirm_SSLDisable'),
                         cancelAction: function () {
-                            PWM_MAIN.getObject('widget_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_SECURE%>').checked=true;
+                            PWM_MAIN.getObject('widget_<%=ConfigGuideFormField.PARAM_LDAP_SECURE%>').checked=true;
                             PWM_MAIN.closeWaitDialog();
                             handleFormActivity();
                         }

+ 5 - 4
src/main/webapp/WEB-INF/jsp/configguide-ldap_testuser.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.tag.conditional.PwmIfTest" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -45,12 +46,12 @@
                 <div class="setting_body">
                     <pwm:display key="ldap_testuser_description" bundle="ConfigGuide"/>
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>" style="padding-left: 5px; padding-top: 5px">
                             Example: <code><%=PwmSetting.LDAP_TEST_USER_DN.getExample(ConfigGuideForm.generateStoredConfig(configGuideBean).getTemplateSet())%></code>
                             <br/><br/>
                             <b>LDAP Test User DN</b>
                             <br/>
-                            <input style="width:400px" class="configStringInput" id="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER%>" name="<%=ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER))%>" autofocus/>
+                            <input style="width:400px" class="configStringInput" id="<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>" name="<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_TEST_USER))%>" autofocus/>
                             <button type="button" class="btn" id="button-browse-testUser">
                                 <span class="btn-icon pwm-icon pwm-icon-sitemap"></span>
                                 <pwm:display key="Button_Browse"/>
@@ -97,14 +98,14 @@
 
         PWM_MAIN.addEventHandler('button-browse-testUser','click',function(){
             UILibrary.editLdapDN(function(value){
-                PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER%>').value = value;
+                PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_TEST_USER%>').value = value;
                 handleFormActivity();
             })
         });
     });
 
     function checkIfNextEnabled() {
-        var fieldValue = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_LDAP_TEST_USER%>').value;
+        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_GLOBAL['pwm-health'] !== 'GOOD' && PWM_GLOBAL['pwm-health'] !== 'CONFIG') {

+ 5 - 4
src/main/webapp/WEB-INF/jsp/configguide-password.jsp

@@ -1,5 +1,6 @@
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
 <%@ page import="password.pwm.http.tag.value.PwmValue" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -44,7 +45,7 @@
                 <pwm:display key="password_description" bundle="ConfigGuide"/>
                 <br/><br/>
                 <form id="configForm">
-                    <input type="hidden" id="<%=ConfigGuideForm.FormParameter.PARAM_CONFIG_PASSWORD%>" name="<%=ConfigGuideForm.FormParameter.PARAM_CONFIG_PASSWORD%>"/>
+                    <input type="hidden" id="<%=ConfigGuideFormField.PARAM_CONFIG_PASSWORD%>" name="<%=ConfigGuideFormField.PARAM_CONFIG_PASSWORD%>"/>
                 </form>
                 <div style="text-align: center"><button class="btn" id="button-setPassword">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-key"></span></pwm:if>
@@ -72,7 +73,7 @@
 
             PWM_MAIN.addEventHandler('button-setPassword','click',function(){
                 var writeFunction = function(password) {
-                    var hiddenInput = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_CONFIG_PASSWORD%>');
+                    var hiddenInput = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_CONFIG_PASSWORD%>');
                     hiddenInput.value = password;
                     PWM_GUIDE.updateForm();
                     checkIfNextEnabled();
@@ -85,9 +86,9 @@
         });
 
         function checkIfNextEnabled() {
-            var password = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_CONFIG_PASSWORD%>').value;
+            var password = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_CONFIG_PASSWORD%>').value;
 
-            <% final String existingPwd = configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_CONFIG_PASSWORD); %>
+            <% final String existingPwd = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_CONFIG_PASSWORD); %>
             <% if (existingPwd == null || existingPwd.isEmpty()) { %>
             PWM_MAIN.getObject('button_next').disabled = true;
             if (password.length > 0) {

+ 0 - 18
src/main/webapp/WEB-INF/jsp/configguide-start.jsp

@@ -90,31 +90,13 @@
     <script type="text/javascript">
         PWM_GLOBAL['startupFunctions'].push(function() {
             PWM_MAIN.addEventHandler('button-startConfigGuide', 'click', function () {
-                if (PWM_GLOBAL['setting-displayEula']) {
-                    PWM_MAIN.showEula(true, function () {
-                        PWM_GUIDE.gotoStep('NEXT');
-                    });
-                } else {
                     PWM_GUIDE.gotoStep('NEXT');
-                }
             });
             PWM_MAIN.addEventHandler('button-manualConfig', 'click', function () {
-                if (PWM_GLOBAL['setting-displayEula']) {
-                    PWM_MAIN.showEula(true,function(){
-                        PWM_GUIDE.skipGuide();
-                    });
-                } else {
                     PWM_GUIDE.skipGuide();
-                }
             });
             PWM_MAIN.addEventHandler('button-uploadConfig', 'click', function () {
-                if (PWM_GLOBAL['setting-displayEula']) {
-                    PWM_MAIN.showEula(true,function(){
-                        PWM_CONFIG.uploadConfigDialog();
-                    });
-                } else {
                     PWM_CONFIG.uploadConfigDialog();
-                }
             });
 
         });

+ 6 - 5
src/main/webapp/WEB-INF/jsp/configguide-storage.jsp

@@ -1,4 +1,5 @@
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -26,7 +27,7 @@
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <% final ConfigGuideBean configGuideBean = JspUtility.getSessionBean(pageContext, ConfigGuideBean.class);%>
-<% final String selectedTemplate = configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_TEMPLATE_STORAGE); %>
+<% final String selectedTemplate = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_TEMPLATE_STORAGE); %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
@@ -38,7 +39,7 @@
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <pwm:display key="Display_ConfigGuideSelectStorage" bundle="Config"/>
             <br/>
-            <select id="<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_STORAGE%>" name="<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_STORAGE%>">
+            <select id="<%=ConfigGuideFormField.PARAM_TEMPLATE_STORAGE%>" name="<%=ConfigGuideFormField.PARAM_TEMPLATE_STORAGE%>">
             <% if (selectedTemplate == null || selectedTemplate.isEmpty()) { %>
             <option value="NOTSELECTED" selected disabled> -- Please select a template -- </option>
             <% } %>
@@ -81,8 +82,8 @@
         }
 
         function getSelectedValue() {
-            var selectedIndex = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_STORAGE%>').selectedIndex;
-            var newTemplate = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_STORAGE%>').options[selectedIndex];
+            var selectedIndex = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TEMPLATE_STORAGE%>').selectedIndex;
+            var newTemplate = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TEMPLATE_STORAGE%>').options[selectedIndex];
             return newTemplate.value;
         }
 
@@ -97,7 +98,7 @@
                 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('<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_STORAGE%>','change',function(){
+                PWM_MAIN.addEventHandler('<%=ConfigGuideFormField.PARAM_TEMPLATE_STORAGE%>','change',function(){
                     formHandler();
                 });
             });

+ 120 - 0
src/main/webapp/WEB-INF/jsp/configguide-telemetry.jsp

@@ -0,0 +1,120 @@
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
+<%@ page import="password.pwm.util.java.JavaHelper" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
+<%@ page import="password.pwm.util.java.StringUtil" %>
+<%--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2017 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
+  --%>
+
+<% JspUtility.setFlag(pageContext, PwmRequestFlag.HIDE_LOCALE); %>
+<% JspUtility.setFlag(pageContext, PwmRequestFlag.INCLUDE_CONFIG_CSS); %>
+<!DOCTYPE html>
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
+<% final ConfigGuideBean configGuideBean = JspUtility.getSessionBean(pageContext, ConfigGuideBean.class);%>
+<%@ taglib uri="pwm" prefix="pwm" %>
+<html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
+<%@ include file="fragment/header.jsp" %>
+<body class="nihilo">
+<div id="wrapper">
+    <%@ include file="fragment/configguide-header.jsp"%>
+    <div id="centerbody">
+        <form id="configForm" name="configForm">
+            <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
+            <div id="outline_ldap-server" class="setting_outline">
+                <div id="titlePaneHeader-ldap-server" class="setting_title">
+                    <div class="setting_title">
+                        Feature Usage Statistics
+                    </div>
+                </div>
+                <div class="setting_body">
+
+                    <label for="widget_<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>">
+                        <b><%=PwmSetting.PUBLISH_STATS_ENABLE.getLabel(JspUtility.locale(request))%></b>
+                    </label>
+                    <br/><br/>
+                    <%=PwmSetting.PUBLISH_STATS_ENABLE.getDescription(JspUtility.locale(request))%>
+                    <br/><br/>
+                    <% final boolean secureChecked = "true".equalsIgnoreCase(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_TELEMETRY_ENABLE));%>
+                    <label class="checkboxWrapper">
+                        <input type="checkbox" id="widget_<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>" name="widget_<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>" <%=secureChecked ? "checked" : ""%>/> Enabled
+                        <input type="hidden" id="<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>" name="<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>" value="false"/>
+                    </label>
+                    <br/><br/>
+
+                    <label for="<%=ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION%>">
+                        <b><%=PwmSetting.PUBLISH_STATS_SITE_DESCRIPTION.getLabel(JspUtility.locale(request))%></b>
+                    </label>
+                    <br/><br/>
+
+                    <%=PwmSetting.PUBLISH_STATS_SITE_DESCRIPTION.getDescription(JspUtility.locale(request))%>
+                    <br/><br/>
+                    <input class="configStringInput" maxlength="100" id="<%=ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION%>" name="<%=ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION%>" value="<%=configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION)%>" <pwm:autofocus/> />
+                    <br/><br/>
+
+                </div>
+            </div>
+            <% String privacyText = JavaHelper.readEulaText(ContextManager.getContextManager(session),PwmConstants.RESOURCE_FILE_PRIVACY_TXT); %>
+            <div id="agreementWrapper" style="display: none">
+            <% if (!StringUtil.isEmpty(privacyText)) { %>
+            <label><b>Data Privacy Policy</b></label>
+            <div id="agreementText" class="eulaText"><%=privacyText%></div>
+            <% } %>
+            </div>
+        </form>
+        <br/>
+        <%@ include file="fragment/configguide-buttonbar.jsp" %>
+    </div>
+    <div class="push"></div>
+</div>
+<pwm:script>
+    <script type="text/javascript">
+        function handleFormActivity() {
+            PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>').value =
+                PWM_MAIN.getObject('widget_<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>').checked ? "true" : "false";
+            PWM_GUIDE.updateForm();
+
+            if (PWM_MAIN.getObject('widget_<%=ConfigGuideFormField.PARAM_TELEMETRY_ENABLE%>').checked) {
+                PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION%>').disabled = false;
+                PWM_MAIN.getObject('agreementWrapper').style.display = 'inline';
+            } else {
+                PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION%>').disabled = true;
+                PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TELEMETRY_DESCRIPTION%>').value = '';
+                PWM_MAIN.getObject('agreementWrapper').style.display = 'none';
+            }
+        }
+
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            PWM_MAIN.addEventHandler('configForm','input,click',function(){
+                handleFormActivity();
+            });
+
+            handleFormActivity();
+
+            PWM_MAIN.addEventHandler('button_next','click',function(){PWM_GUIDE.gotoStep('NEXT')});
+            PWM_MAIN.addEventHandler('button_previous','click',function(){PWM_GUIDE.gotoStep('PREVIOUS')});
+        });
+
+
+    </script>
+</pwm:script>
+<%@ include file="fragment/footer.jsp" %>
+</body>
+</html>

+ 6 - 5
src/main/webapp/WEB-INF/jsp/configguide-template.jsp

@@ -1,4 +1,5 @@
 <%@ page import="password.pwm.http.servlet.configguide.ConfigGuideForm" %>
+<%@ page import="password.pwm.http.servlet.configguide.ConfigGuideFormField" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -27,7 +28,7 @@
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <% final ConfigGuideBean configGuideBean = JspUtility.getSessionBean(pageContext, ConfigGuideBean.class);%>
-<% final String selectedTemplate = configGuideBean.getFormData().get(ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP); %>
+<% final String selectedTemplate = configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_TEMPLATE_LDAP); %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">
@@ -38,7 +39,7 @@
         <pwm:display key="template_description" bundle="ConfigGuide"/>
         <br/>
         <form id="configForm">
-            <select id="<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP%>" name="<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP%>" style="width:300px">
+            <select id="<%=ConfigGuideFormField.PARAM_TEMPLATE_LDAP%>" name="<%=ConfigGuideFormField.PARAM_TEMPLATE_LDAP%>" style="width:300px">
                 <% if (selectedTemplate == null || selectedTemplate.isEmpty()) { %>
                 <option value="NOTSELECTED" selected disabled>-- Please select a template --</option>
                 <% } %>
@@ -63,8 +64,8 @@
         }
 
         function getSelectedValue() {
-            var selectedIndex = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP%>').selectedIndex;
-            var newTemplate = PWM_MAIN.getObject('<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP%>').options[selectedIndex];
+            var selectedIndex = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TEMPLATE_LDAP%>').selectedIndex;
+            var newTemplate = PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_TEMPLATE_LDAP%>').options[selectedIndex];
             return newTemplate.value;
         }
 
@@ -84,7 +85,7 @@
                 });
             });
             PWM_MAIN.addEventHandler('button_next','click',function(){PWM_GUIDE.gotoStep('NEXT')});
-            PWM_MAIN.addEventHandler('<%=ConfigGuideForm.FormParameter.PARAM_TEMPLATE_LDAP%>','change',function(){formHandler()});
+            PWM_MAIN.addEventHandler('<%=ConfigGuideFormField.PARAM_TEMPLATE_LDAP%>','change',function(){formHandler()});
             updateNextButton();
         });
     </script>

+ 1 - 0
src/main/webapp/WEB-INF/jsp/error-http.jsp

@@ -35,6 +35,7 @@
 <%@ include file="fragment/header.jsp" %>
 <% response.setHeader("Content-Encoding",""); //remove gzip encoding header %>
 <% final int statusCode = pageContext.getErrorData().getStatusCode(); %>
+
 <body class="nihilo" data-jsp-page="error-http.jsp">
 <div id="wrapper">
     <jsp:include page="fragment/header-body.jsp">

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

@@ -69,7 +69,7 @@
     <script>
         PWM_GLOBAL['startupFunctions'].push(function(){
             PWM_MAIN.addEventHandler('button-cancel','click',function() {
-                PWM_MAIN.submitPostAction('NewUser', '<%=NewUserServlet.NewUserAction.reset%>');
+                PWM_MAIN.submitPostAction('newuser', '<%=NewUserServlet.NewUserAction.reset%>');
             });
         });
     </script>

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

@@ -31,7 +31,7 @@
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%
     final PwmRequest pwmRequest = PwmRequest.forRequest(request, response);
-    final Map<String,NewUserProfile> newUserProfiles = pwmRequest.getConfig().getNewUserProfiles();
+    final Map<String,String> newUserProfiles = (Map)pwmRequest.getAttribute(PwmRequestAttribute.NewUser_VisibleProfiles);
 %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
@@ -50,16 +50,16 @@
             <colgroup>
 
             </colgroup>
-            <% for (final NewUserProfile profile : newUserProfiles.values()) { %>
+            <% for (final String profileID : newUserProfiles.values()) { %>
             <tr>
                 <td>
                     <form action="<pwm:current-url/>" method="post" class="pwm-form"
                           enctype="application/x-www-form-urlencoded" name="search">
                         <button class="btn" type="submit" name="submitBtn">
                             <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
-                            <%=profile.getDisplayName(pwmRequest.getLocale())%>
+                            <%=newUserProfiles.get(profileID)%>
                         </button>
-                        <input type="hidden" name="profile" value="<%=profile.getIdentifier()%>"/>
+                        <input type="hidden" name="profile" value="<%=profileID%>"/>
                         <input type="hidden" name="processAction" value="<%=NewUserServlet.NewUserAction.profileChoice%>"/>
                         <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
                     </form>

+ 0 - 5
src/main/webapp/WEB-INF/web.xml

@@ -207,11 +207,6 @@
         <listener-class>password.pwm.http.HttpEventManager</listener-class>
     </listener>
     <error-page>
-        <error-code>404</error-code>
-        <location>/WEB-INF/jsp/error-http.jsp</location>
-    </error-page>
-    <error-page>
-        <error-code>500</error-code>
         <location>/WEB-INF/jsp/error-http.jsp</location>
     </error-page>
     <session-config>

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

@@ -705,6 +705,16 @@ margin-right: 20px;
     font-family: monospace;
 }
 
+.eulaText {
+    border: 0;
+    background-color: #DDDDDD;
+    border-radius: 7px;
+    padding: 10px;
+    max-height: 300px;
+    overflow: auto;
+    font-family: monospace;
+    white-space: pre-wrap;
+}
 
 /* hide recaptcha iframe near footer */
 body > iframe[src="about:blank"] {

+ 9 - 1
src/main/webapp/public/resources/text/eula.txt

@@ -1 +1,9 @@
-EULA TEXT
+EULA TEXT.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum gravida sapien sed lacinia iaculis. Etiam vitae est ipsum. Integer cursus velit eu lectus dapibus eleifend. Nulla sit amet auctor sem. Nulla et vulputate nibh. Integer justo lectus, dapibus quis justo vitae, tincidunt sagittis diam. Quisque fringilla, libero non lobortis dignissim, est ligula vestibulum ex, eu dictum justo eros eu urna. Integer tristique sapien nec nulla commodo, vitae tincidunt lorem commodo. Mauris iaculis lacinia rutrum. Fusce interdum felis in imperdiet consectetur. Sed sagittis elementum luctus. Fusce pulvinar maximus imperdiet. Pellentesque posuere dictum fermentum. Mauris velit eros, rutrum quis dui tincidunt, hendrerit pulvinar dolor.
+
+Phasellus id malesuada purus. Integer at condimentum lorem. Aliquam ullamcorper quam libero, eget aliquet metus bibendum ut. Aenean a enim sollicitudin, blandit diam sit amet, interdum dui. Quisque enim lorem, ullamcorper in imperdiet vitae, gravida rhoncus odio. Ut id velit pulvinar, sollicitudin lorem nec, ultricies lacus. Aliquam venenatis in dui id egestas. Nulla in tincidunt lorem, non molestie quam. Ut non metus at ex vehicula porttitor. Donec vel vulputate nulla. Etiam venenatis sapien ac justo iaculis tincidunt.
+
+Donec ante augue, viverra sit amet pellentesque ac, imperdiet quis mi. Nunc aliquam orci eu dolor consequat, at pellentesque orci feugiat. Vivamus leo nisl, fringilla eu pellentesque id, ullamcorper ornare neque. Integer dictum efficitur tristique. Sed porta lectus ut metus congue, id commodo ex eleifend. Curabitur faucibus nunc at sapien tincidunt, vel auctor dolor placerat. Nullam nisl nisl, pharetra sed augue vitae, luctus facilisis ligula. Aenean mollis luctus ipsum, nec dignissim ligula tempus non. Nunc ut maximus lorem, ut dictum purus. Nulla gravida arcu ac magna gravida accumsan. Integer et feugiat justo. Vestibulum cursus luctus ipsum, et finibus erat elementum a. Vestibulum varius nisl sed nisl dictum, ut pellentesque purus aliquam. Aenean porttitor, lorem eget pulvinar fringilla, sem mauris ultrices orci, id elementum massa mauris sit amet est.
+
+Cras rutrum nunc sem, id hendrerit urna iaculis eget. Nulla facilisi. Sed at tortor ac felis pretium dapibus at at purus. Donec justo sapien, viverra eu viverra nec, laoreet eget mauris. Morbi in fermentum tortor. Aenean vitae ultricies erat. Cras faucibus semper leo vitae consequat.

+ 0 - 0
src/main/webapp/public/resources/text/privacy.txt