Browse Source

Merge branch 'master' of github.com:pwm-project/pwm into ux-changes

Joseph White 8 years ago
parent
commit
67c7bd27c9
100 changed files with 1661 additions and 795 deletions
  1. 0 6
      checkstyle.xml
  2. 3 0
      import-control.xml
  3. 80 19
      pom.xml
  4. 1 1
      src/main/angular/package.json
  5. 6 1
      src/main/java/password/pwm/AppProperty.java
  6. 8 0
      src/main/java/password/pwm/PwmAboutProperty.java
  7. 3 1
      src/main/java/password/pwm/PwmApplication.java
  8. 4 0
      src/main/java/password/pwm/PwmConstants.java
  9. 1 0
      src/main/java/password/pwm/bean/SessionLabel.java
  10. 11 13
      src/main/java/password/pwm/bean/TelemetryPublishBean.java
  11. 10 0
      src/main/java/password/pwm/config/Configuration.java
  12. 66 0
      src/main/java/password/pwm/config/CustomLinkConfiguration.java
  13. 32 9
      src/main/java/password/pwm/config/FormUtility.java
  14. 12 1
      src/main/java/password/pwm/config/PwmSetting.java
  15. 3 0
      src/main/java/password/pwm/config/PwmSettingFlag.java
  16. 1 1
      src/main/java/password/pwm/config/PwmSettingProperty.java
  17. 4 0
      src/main/java/password/pwm/config/PwmSettingSyntax.java
  18. 2 0
      src/main/java/password/pwm/config/SettingReader.java
  19. 1 1
      src/main/java/password/pwm/config/function/ActionCertImportFunction.java
  20. 84 0
      src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java
  21. 1 1
      src/main/java/password/pwm/config/function/UserMatchViewerFunction.java
  22. 2 1
      src/main/java/password/pwm/config/option/WebServiceUsage.java
  23. 3 3
      src/main/java/password/pwm/config/profile/AbstractProfile.java
  24. 2 2
      src/main/java/password/pwm/config/profile/ChallengeProfile.java
  25. 1 1
      src/main/java/password/pwm/config/profile/LdapProfile.java
  26. 1 1
      src/main/java/password/pwm/config/profile/Profile.java
  27. 1 1
      src/main/java/password/pwm/config/profile/ProfileUtility.java
  28. 1 1
      src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java
  29. 10 10
      src/main/java/password/pwm/config/value/ActionValue.java
  30. 1 1
      src/main/java/password/pwm/config/value/ChallengeValue.java
  31. 137 0
      src/main/java/password/pwm/config/value/CustomLinkValue.java
  32. 5 3
      src/main/java/password/pwm/config/value/FormValue.java
  33. 29 3
      src/main/java/password/pwm/config/value/NamedSecretValue.java
  34. 158 0
      src/main/java/password/pwm/config/value/RemoteWebServiceValue.java
  35. 1 1
      src/main/java/password/pwm/config/value/StringValue.java
  36. 1 1
      src/main/java/password/pwm/config/value/UserPermissionValue.java
  37. 5 45
      src/main/java/password/pwm/config/value/data/ActionConfiguration.java
  38. 4 29
      src/main/java/password/pwm/config/value/data/ChallengeItemConfiguration.java
  39. 44 87
      src/main/java/password/pwm/config/value/data/FormConfiguration.java
  40. 1 1
      src/main/java/password/pwm/config/value/data/NamedSecretData.java
  41. 50 0
      src/main/java/password/pwm/config/value/data/RemoteWebServiceConfiguration.java
  42. 5 25
      src/main/java/password/pwm/config/value/data/ShortcutItem.java
  43. 1 1
      src/main/java/password/pwm/config/value/data/UserPermission.java
  44. 1 0
      src/main/java/password/pwm/error/PwmError.java
  45. 1 1
      src/main/java/password/pwm/health/CertificateChecker.java
  46. 10 4
      src/main/java/password/pwm/health/LDAPStatusChecker.java
  47. 4 0
      src/main/java/password/pwm/http/ContextManager.java
  48. 11 1
      src/main/java/password/pwm/http/PwmRequest.java
  49. 3 0
      src/main/java/password/pwm/http/PwmRequestAttribute.java
  50. 7 2
      src/main/java/password/pwm/http/PwmSessionWrapper.java
  51. 7 0
      src/main/java/password/pwm/http/PwmURL.java
  52. 1 1
      src/main/java/password/pwm/http/SessionManager.java
  53. 6 49
      src/main/java/password/pwm/http/bean/ConfigGuideBean.java
  54. 1 1
      src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java
  55. 2 1
      src/main/java/password/pwm/http/bean/NewUserBean.java
  56. 1 1
      src/main/java/password/pwm/http/bean/ShortcutsBean.java
  57. 45 41
      src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  58. 1 1
      src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java
  59. 1 1
      src/main/java/password/pwm/http/servlet/AccountInformationServlet.java
  60. 3 3
      src/main/java/password/pwm/http/servlet/ActivateUserServlet.java
  61. 2 2
      src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java
  62. 1 1
      src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  63. 3 4
      src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  64. 1 1
      src/main/java/password/pwm/http/servlet/ShortcutServlet.java
  65. 13 3
      src/main/java/password/pwm/http/servlet/UpdateProfileServlet.java
  66. 1 1
      src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  67. 1 1
      src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  68. 5 0
      src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  69. 34 63
      src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java
  70. 65 0
      src/main/java/password/pwm/http/servlet/configguide/ConfigGuideFormField.java
  71. 15 14
      src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  72. 20 1
      src/main/java/password/pwm/http/servlet/configguide/GuideStep.java
  73. 1 1
      src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java
  74. 2 2
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  75. 1 1
      src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  76. 1 1
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java
  77. 2 2
      src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  78. 17 6
      src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java
  79. 63 14
      src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  80. 13 1
      src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  81. 1 1
      src/main/java/password/pwm/http/servlet/peoplesearch/AttributeDetailBean.java
  82. 6 24
      src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartDataBean.java
  83. 5 24
      src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartReferenceBean.java
  84. 26 40
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java
  85. 21 10
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  86. 3 3
      src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  87. 1 1
      src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  88. 2 2
      src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  89. 1 1
      src/main/java/password/pwm/ldap/LdapPermissionTester.java
  90. 2 2
      src/main/java/password/pwm/ldap/UserInfoReader.java
  91. 1 1
      src/main/java/password/pwm/ldap/search/SearchConfiguration.java
  92. 2 2
      src/main/java/password/pwm/ldap/search/UserSearchEngine.java
  93. 1 1
      src/main/java/password/pwm/ldap/search/UserSearchResults.java
  94. 87 0
      src/main/java/password/pwm/svc/PwmServiceEnum.java
  95. 2 79
      src/main/java/password/pwm/svc/PwmServiceManager.java
  96. 1 1
      src/main/java/password/pwm/svc/intruder/IntruderManager.java
  97. 11 106
      src/main/java/password/pwm/svc/stats/StatisticsManager.java
  98. 207 0
      src/main/java/password/pwm/svc/telemetry/FtpTelemetrySender.java
  99. 86 0
      src/main/java/password/pwm/svc/telemetry/HttpTelemetrySender.java
  100. 33 0
      src/main/java/password/pwm/svc/telemetry/TelemetrySender.java

+ 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"/>

+ 80 - 19
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>
@@ -297,7 +293,7 @@
                         <artifactId>jspc-compiler-tomcat7</artifactId>
                         <version>2.0.2</version>
                     </dependency>
-				</dependencies>
+                </dependencies>
                 <configuration>
                     <includeInProject>false</includeInProject>
                 </configuration>
@@ -505,6 +501,66 @@
                     </execution>
                 </executions>
             </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.0.2</version>
+                <executions>
+                    <execution>
+                        <id>make-a-jar</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-install-plugin</artifactId>
+                <version>2.5.2</version>
+                <executions>
+                    <execution>
+                        <phase>install</phase>
+                        <goals>
+                            <goal>install-file</goal>
+                        </goals>
+                        <configuration>
+                            <packaging>jar</packaging>
+                            <artifactId>${project.artifactId}</artifactId>
+                            <groupId>${project.groupId}</groupId>
+                            <version>${project.version}</version>
+                            <file>
+                                ${project.build.directory}/${project.artifactId}-${project.version}.jar
+                            </file>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>2.8.2</version>
+                <executions>
+                    <execution>
+                        <phase>deploy</phase>
+                        <goals>
+                            <goal>deploy-file</goal>
+                        </goals>
+                        <configuration>
+                            <packaging>jar</packaging>
+                            <generatePom>true</generatePom>
+                            <url>${project.distributionManagement.repository.url}</url>
+                            <artifactId>${project.artifactId}</artifactId>
+                            <groupId>${project.groupId}</groupId>
+                            <version>${project.version}</version>
+                            <file>${project.build.directory}/${project.artifactId}-${project.version}.jar</file>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
         </plugins>
     </build>
 
@@ -532,7 +588,7 @@
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
-            <version>1.16.16</version>
+            <version>1.16.18</version>
             <scope>provided</scope>
         </dependency>
 
@@ -609,7 +665,12 @@
         <dependency>
             <groupId>com.github.ldapchai</groupId>
             <artifactId>ldapchai</artifactId>
-            <version>0.6.9</version>
+            <version>0.6.10</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-net</groupId>
+            <artifactId>commons-net</artifactId>
+            <version>3.6</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -619,12 +680,12 @@
         <dependency>
             <groupId>commons-fileupload</groupId>
             <artifactId>commons-fileupload</artifactId>
-            <version>1.3.2</version>
+            <version>1.3.3</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
-            <version>3.5</version>
+            <version>3.6</version>
         </dependency>
         <dependency>
             <groupId>com.sun.mail</groupId>
@@ -654,12 +715,17 @@
         <dependency>
             <groupId>org.glassfish.jersey.containers</groupId>
             <artifactId>jersey-container-servlet</artifactId>
-            <version>2.26-b02</version>
+            <version>2.26-b07</version>
         </dependency>
         <dependency>
             <groupId>org.glassfish.jersey.media</groupId>
             <artifactId>jersey-media-json-jackson</artifactId>
-            <version>2.26-b02</version>
+            <version>2.26-b07</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+            <version>2.26-b07</version>
         </dependency>
         <dependency>
             <groupId>org.jasig.cas.client</groupId>
@@ -696,11 +762,6 @@
             <artifactId>jdom2</artifactId>
             <version>2.0.6</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.derby</groupId>
-            <artifactId>derby</artifactId>
-            <version>10.13.1.1</version>
-        </dependency>
         <dependency>
             <groupId>org.xeustechnologies</groupId>
             <artifactId>jcl-core</artifactId>
@@ -714,7 +775,7 @@
         <dependency>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
-            <version>2.8.0</version>
+            <version>2.8.1</version>
         </dependency>
         <dependency>
             <groupId>eu.bitwalker</groupId>
@@ -724,7 +785,7 @@
         <dependency>
             <groupId>org.jetbrains.xodus</groupId>
             <artifactId>xodus-environment</artifactId>
-            <version>1.0.5</version>
+            <version>1.0.6</version>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -739,7 +800,7 @@
         <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
-            <version>2.5.2</version>
+            <version>2.5.3</version>
         </dependency>
 
 

+ 1 - 1
src/main/angular/package.json

@@ -19,7 +19,7 @@
   "license": "ISC",
   "dependencies": {},
   "devDependencies": {
-    "@types/angular": "1.5.8",
+    "@types/angular": "1.6.6",
     "@types/angular-mocks": "1.5.5",
     "@types/angular-translate": "2.4.33",
     "@types/angular-ui-router": "1.1.34",

+ 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. */

+ 8 - 0
src/main/java/password/pwm/PwmAboutProperty.java

@@ -69,6 +69,7 @@ public enum PwmAboutProperty {
     app_configurationRestartCounter,
     app_secureBlockAlgorithm,
     app_secureHashAlgorithm,
+    app_ldapProfileCount,
 
     build_Time,
     build_Number,
@@ -92,6 +93,7 @@ public enum PwmAboutProperty {
     java_osVersion,
     java_randomAlgorithm,
     java_defaultCharset,
+    java_appServerInfo,
 
     database_driverName,
     database_driverVersion,
@@ -113,6 +115,7 @@ public enum PwmAboutProperty {
         aboutMap.put(app_startTime,                dateFormatForInfoBean(pwmApplication.getStartupTime()));
         aboutMap.put(app_installTime,              dateFormatForInfoBean(pwmApplication.getInstallTime()));
         aboutMap.put(app_siteUrl,                  pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_SITE_URL));
+        aboutMap.put(app_ldapProfileCount,         Integer.toString(pwmApplication.getConfig().getLdapProfiles().size()));
         aboutMap.put(app_instanceID,               pwmApplication.getInstanceID());
         aboutMap.put(app_trialMode,                Boolean.toString(PwmConstants.TRIAL_MODE));
         if (pwmApplication.getPwmEnvironment() != null) {
@@ -210,6 +213,11 @@ public enum PwmAboutProperty {
             }
         }
 
+        if (pwmApplication.getPwmEnvironment().getContextManager() != null
+                && pwmApplication.getPwmEnvironment().getContextManager().getServerInfo() != null) {
+            aboutMap.put(java_appServerInfo, pwmApplication.getPwmEnvironment().getContextManager().getServerInfo());
+        }
+
         return Collections.unmodifiableMap(aboutMap);
     }
 

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

@@ -54,12 +54,12 @@ import password.pwm.svc.sessiontrack.SessionTrackService;
 import password.pwm.svc.shorturl.UrlShortenerService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.telemetry.VersionChecker;
 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.PasswordData;
-import password.pwm.util.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;

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

@@ -170,6 +170,10 @@ 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";
+    public static final String RESOURCE_FILE_WELCOME_TXT = "welcome.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;

+ 11 - 13
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,17 @@ import java.util.List;
 import java.util.Map;
 
 @Getter
-@AllArgsConstructor
-public class StatsPublishBean implements Serializable {
-    private final String instanceID;
+@Builder
+public class TelemetryPublishBean implements Serializable {
     private final Instant timestamp;
-    private final Map<String,String> totalStatistics;
+    private final String id;
+    private final String instanceHash;
+    private final String siteDescription;
+    private final Instant installTime;
+    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;
-
-    public enum KEYS {
-        SITE_URL,
-        SITE_DESCRIPTION,
-        INSTALL_DATE,
-        LDAP_VENDOR
-    }
+    private final Map<String,String> about;
 }

+ 10 - 0
src/main/java/password/pwm/config/Configuration.java

@@ -44,6 +44,7 @@ import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordRule;
 import password.pwm.config.profile.UpdateAttributesProfile;
 import password.pwm.config.stored.ConfigurationProperty;
+import password.pwm.config.value.CustomLinkValue;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.BooleanValue;
@@ -57,6 +58,10 @@ import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.UserPermissionValue;
+import password.pwm.config.value.data.ActionConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.config.value.data.NamedSecretData;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -259,6 +264,11 @@ public class Configuration implements Serializable, SettingReader {
             if (value == null) {
                 return null;
             }
+
+            if (value instanceof CustomLinkValue) {
+                return (List<FormConfiguration>)value.toNativeObject();
+            }
+
             if ((!(value instanceof FormValue))) {
                 throw new IllegalArgumentException("setting value is not readable as form");
             }

+ 66 - 0
src/main/java/password/pwm/config/CustomLinkConfiguration.java

@@ -0,0 +1,66 @@
+/*
+ * 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.config;
+
+import lombok.Getter;
+import password.pwm.util.LocaleHelper;
+import password.pwm.util.java.JsonUtil;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * @author Richard A. Keil
+ */
+@Getter
+public class CustomLinkConfiguration implements Serializable {
+
+    public enum Type {text, url, select, checkbox, customLink}
+
+    private String name;
+    private Type type = Type.customLink;
+    private Map<String,String> labels = Collections.singletonMap("", "");
+    private Map<String,String> description = Collections.singletonMap("","");
+    private String url = "";
+    private boolean newWindow;
+    private Map<String,String> selectOptions = Collections.emptyMap();
+
+    public String getLabel(final Locale locale) {
+        return LocaleHelper.resolveStringKeyLocaleMap(locale, labels);
+    }
+
+    public String getDescription(final Locale locale) {
+        return LocaleHelper.resolveStringKeyLocaleMap(locale, description);
+    }
+
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+
+        sb.append("CustomLink: ");
+        sb.append(JsonUtil.serialize(this));
+
+        return sb.toString();
+    }
+}

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

@@ -31,6 +31,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
@@ -144,7 +145,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 +157,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 +179,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 +273,7 @@ public class FormUtility {
                     resultSearchSizeLimit,
                     Collections.emptyList(),
                     SessionLabel.SYSTEM_LABEL
-                    ));
+            ));
 
             if (excludeDN != null && !excludeDN.isEmpty()) {
                 for (final UserIdentity loopIgnoreIdentity : excludeDN) {

+ 12 - 1
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(
@@ -865,6 +867,8 @@ public enum PwmSetting {
     UPDATE_PROFILE_SMS_VERIFICATION(
             "updateAttributes.sms.verification", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UPDATE_PROFILE),
 
+    UPDATE_PROFILE_CUSTOMLINKS(
+            "updateAttributes.customLinks", PwmSettingSyntax.CUSTOMLINKS, PwmSettingCategory.UPDATE_PROFILE),
 
     // shortcut settings
     SHORTCUT_ENABLE(
@@ -915,6 +919,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),
 
 
 
@@ -1071,7 +1077,10 @@ public enum PwmSetting {
     // CAS SSO
     CAS_CLEAR_PASS_URL(
             "cas.clearPassUrl", PwmSettingSyntax.STRING, PwmSettingCategory.CAS_SSO),
-
+    CAS_CLEARPASS_KEY(
+            "cas.clearPass.key", PwmSettingSyntax.FILE, PwmSettingCategory.CAS_SSO),
+    CAS_CLEARPASS_ALGORITHM(
+            "cas.clearPass.alg", PwmSettingSyntax.STRING, PwmSettingCategory.CAS_SSO),
     // http sso
     SSO_AUTH_HEADER_NAME(
             "security.sso.authHeaderName", PwmSettingSyntax.STRING, PwmSettingCategory.HTTP_SSO),
@@ -1109,6 +1118,8 @@ public enum PwmSetting {
             "external.macros.urls", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.REST_CLIENT),
     EXTERNAL_MACROS_REMOTE_RESPONSES_URL(
             "external.remoteResponses.url", PwmSettingSyntax.STRING, PwmSettingCategory.REST_CLIENT),
+    EXTERNAL_REMOTE_DATA_URL(
+            "external.remoteData.url", PwmSettingSyntax.REMOTE_WEB_SERVICE, PwmSettingCategory.REST_CLIENT),
 
 
     //appliance

+ 3 - 0
src/main/java/password/pwm/config/PwmSettingFlag.java

@@ -49,7 +49,10 @@ public enum PwmSettingFlag {
     Form_ShowRequiredOption,
     Form_ShowMultiValueOption,
     Form_HideStandardOptions,
+    Form_ShowSource,
 
     Verification_HideMinimumOptional,
 
+    WebService_NoBody,
+
 }

+ 1 - 1
src/main/java/password/pwm/config/PwmSettingProperty.java

@@ -33,5 +33,5 @@ public enum PwmSettingProperty {
 
     Cert_ImportHandler,
 
-
+    MethodType,
 }

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

@@ -25,6 +25,7 @@ package password.pwm.config;
 import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.ChallengeValue;
+import password.pwm.config.value.CustomLinkValue;
 import password.pwm.config.value.EmailValue;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.FormValue;
@@ -35,6 +36,7 @@ import password.pwm.config.value.NumericValue;
 import password.pwm.config.value.OptionListValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PrivateKeyValue;
+import password.pwm.config.value.RemoteWebServiceValue;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.UserPermissionValue;
@@ -70,6 +72,8 @@ public enum PwmSettingSyntax {
     VERIFICATION_METHOD(VerificationMethodValue.factory()),
     PRIVATE_KEY(PrivateKeyValue.factory()),
     NAMED_SECRET(NamedSecretValue.factory()),
+    CUSTOMLINKS(CustomLinkValue.factory()),
+    REMOTE_WEB_SERVICE(RemoteWebServiceValue.factory()),
 
     ;
 

+ 2 - 0
src/main/java/password/pwm/config/SettingReader.java

@@ -22,6 +22,8 @@
 
 package password.pwm.config;
 
+import password.pwm.config.value.data.ActionConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.util.PasswordData;
 
 import java.security.cert.X509Certificate;

+ 1 - 1
src/main/java/password/pwm/config/function/ActionCertImportFunction.java

@@ -23,7 +23,7 @@
 package password.pwm.config.function;
 
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.ActionValue;

+ 84 - 0
src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java

@@ -0,0 +1,84 @@
+/*
+ * 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.config.function;
+
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.config.value.RemoteWebServiceValue;
+import password.pwm.config.value.data.RemoteWebServiceConfiguration;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JsonUtil;
+
+import java.net.URI;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RemoteWebServiceCertImportFunction extends AbstractUriCertImportFunction {
+
+    @Override
+    String getUri(final StoredConfigurationImpl storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData) throws PwmOperationalException {
+        final RemoteWebServiceValue actionValue = (RemoteWebServiceValue)storedConfiguration.readSetting(pwmSetting, profile);
+        final String serviceName = actionNameFromExtraData(extraData);
+        final RemoteWebServiceConfiguration action =  actionValue.forName(serviceName);
+        final String uriString = action.getUrl();
+
+        if (uriString == null || uriString.isEmpty()) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,"Setting " + pwmSetting.toMenuLocationDebug(profile, null) + " action " + serviceName + " must first be configured");
+            throw new PwmOperationalException(errorInformation);
+        }
+        try {
+            URI.create(uriString);
+        } catch (IllegalArgumentException e) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,"Setting " + pwmSetting.toMenuLocationDebug(profile, null) + " action " + serviceName + " has an invalid URL syntax");
+            throw new PwmOperationalException(errorInformation);
+        }
+        return uriString;
+    }
+
+    private String actionNameFromExtraData(final String extraData) {
+        return extraData;
+    }
+
+    void store(final X509Certificate[] certs, final StoredConfigurationImpl storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData, final UserIdentity userIdentity) throws PwmOperationalException, PwmUnrecoverableException {
+        final RemoteWebServiceValue actionValue = (RemoteWebServiceValue)storedConfiguration.readSetting(pwmSetting, profile);
+        final String actionName = actionNameFromExtraData(extraData);
+        final List<RemoteWebServiceConfiguration> newList = new ArrayList<>();
+        for (final RemoteWebServiceConfiguration loopConfiguration : actionValue.toNativeObject()) {
+            if (actionName.equals(loopConfiguration.getName())) {
+                final RemoteWebServiceConfiguration newConfig = JsonUtil.cloneUsingJson(loopConfiguration, RemoteWebServiceConfiguration.class);
+                newConfig.setCertificates(certs);
+                newList.add(newConfig);
+            } else {
+                newList.add(JsonUtil.cloneUsingJson(loopConfiguration,RemoteWebServiceConfiguration.class));
+            }
+        }
+        final RemoteWebServiceValue newActionValue = new RemoteWebServiceValue(newList);
+        storedConfiguration.writeSetting(pwmSetting, profile, newActionValue, userIdentity);
+    }
+
+}

+ 1 - 1
src/main/java/password/pwm/config/function/UserMatchViewerFunction.java

@@ -33,7 +33,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;

+ 2 - 1
src/main/java/password/pwm/config/option/WebServiceUsage.java

@@ -23,5 +23,6 @@
 package password.pwm.config.option;
 
 public enum WebServiceUsage {
-    SigningForm
+    Health,
+    SigningForm,
 }

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

@@ -22,15 +22,15 @@
 
 package password.pwm.config.profile;
 
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.SettingReader;
 import password.pwm.config.StoredValue;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.VerificationMethodValue;

+ 2 - 2
src/main/java/password/pwm/config/profile/ChallengeProfile.java

@@ -28,11 +28,11 @@ import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.exception.ChaiValidationException;
 import password.pwm.PwmConstants;
-import password.pwm.config.ChallengeItemConfiguration;
+import password.pwm.config.value.data.ChallengeItemConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.ChallengeValue;
 import password.pwm.error.ErrorInformation;

+ 1 - 1
src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -32,7 +32,7 @@ import password.pwm.PwmApplication;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.StoredValue;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapPermissionTester;

+ 1 - 1
src/main/java/password/pwm/config/profile/Profile.java

@@ -22,7 +22,7 @@
 
 package password.pwm.config.profile;
 
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 
 import java.io.Serializable;
 import java.util.List;

+ 1 - 1
src/main/java/password/pwm/config/profile/ProfileUtility.java

@@ -28,7 +28,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.util.logging.PwmLogger;

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

@@ -27,7 +27,7 @@ import com.novell.ldapchai.ChaiPasswordRule;
 import com.novell.ldapchai.util.DefaultChaiPasswordPolicy;
 import com.novell.ldapchai.util.PasswordRuleHelper;
 import com.novell.ldapchai.util.StringHelper;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;

+ 10 - 10
src/main/java/password/pwm/config/value/ActionValue.java

@@ -24,7 +24,7 @@ package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
 import org.jdom2.Element;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.StoredValue;
@@ -57,14 +57,14 @@ public class ActionValue extends AbstractValue implements StoredValue {
             public ActionValue fromJson(final String input)
             {
                 if (input == null) {
-                    return new ActionValue(Collections.<ActionConfiguration>emptyList());
+                    return new ActionValue(Collections.emptyList());
                 } else {
                     List<ActionConfiguration> srcList = JsonUtil.deserialize(input,
                             new TypeToken<List<ActionConfiguration>>() {
                             }
                     );
 
-                    srcList = srcList == null ? Collections.<ActionConfiguration>emptyList() : srcList;
+                    srcList = srcList == null ? Collections.emptyList() : srcList;
                     while (srcList.contains(null)) {
                         srcList.remove(null);
                     }
@@ -155,18 +155,18 @@ public class ActionValue extends AbstractValue implements StoredValue {
             switch (actionConfiguration.getType()) {
                 case webservice: {
                     sb.append("WebService: ");
-                    sb.append("method=" + actionConfiguration.getMethod());
-                    sb.append(" url=" + actionConfiguration.getUrl());
-                    sb.append(" headers=" + JsonUtil.serializeMap(actionConfiguration.getHeaders()));
-                    sb.append(" body=" + actionConfiguration.getBody());
+                    sb.append("method=").append(actionConfiguration.getMethod());
+                    sb.append(" url=").append(actionConfiguration.getUrl());
+                    sb.append(" headers=").append(JsonUtil.serializeMap(actionConfiguration.getHeaders()));
+                    sb.append(" body=").append(actionConfiguration.getBody());
                 }
                 break;
 
                 case ldap: {
                     sb.append("LDAP: ");
-                    sb.append("method=" + actionConfiguration.getLdapMethod());
-                    sb.append(" attribute=" + actionConfiguration.getAttributeName());
-                    sb.append(" value=" + actionConfiguration.getAttributeValue());
+                    sb.append("method=").append(actionConfiguration.getLdapMethod());
+                    sb.append(" attribute=").append(actionConfiguration.getAttributeName());
+                    sb.append(" value=").append(actionConfiguration.getAttributeValue());
                 }
                 break;
 

+ 1 - 1
src/main/java/password/pwm/config/value/ChallengeValue.java

@@ -25,7 +25,7 @@ package password.pwm.config.value;
 import com.google.gson.reflect.TypeToken;
 import org.jdom2.CDATA;
 import org.jdom2.Element;
-import password.pwm.config.ChallengeItemConfiguration;
+import password.pwm.config.value.data.ChallengeItemConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.util.java.JsonUtil;

+ 137 - 0
src/main/java/password/pwm/config/value/CustomLinkValue.java

@@ -0,0 +1,137 @@
+/*
+ * 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.config.value;
+
+import com.google.gson.reflect.TypeToken;
+import org.jdom2.Element;
+import password.pwm.config.CustomLinkConfiguration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.StoredValue;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.secure.PwmSecurityKey;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+public class CustomLinkValue extends AbstractValue implements StoredValue {
+    final List<CustomLinkConfiguration> values;
+
+    public CustomLinkValue(final List<CustomLinkConfiguration> values) {
+        this.values = values;
+    }
+
+    public static StoredValueFactory factory()
+    {
+        return new StoredValueFactory() {
+            public CustomLinkValue fromJson(final String input)
+            {
+                if (input == null) {
+                    return new CustomLinkValue(Collections.emptyList());
+                } else {
+                    List<CustomLinkConfiguration> srcList = JsonUtil.deserialize(input, new TypeToken<List<CustomLinkConfiguration>>() {
+                    });
+                    srcList = srcList == null ? Collections.emptyList() : srcList;
+                    while (srcList.contains(null)) {
+                        srcList.remove(null);
+                    }
+                    return new CustomLinkValue(Collections.unmodifiableList(srcList));
+                }
+            }
+
+            public CustomLinkValue fromXmlElement(final Element settingElement, final PwmSecurityKey key)
+                    throws PwmOperationalException
+            {
+                final List valueElements = settingElement.getChildren("value");
+                final List<CustomLinkConfiguration> values = new ArrayList<>();
+                for (final Object loopValue : valueElements) {
+                    final Element loopValueElement = (Element) loopValue;
+                    final String value = loopValueElement.getText();
+                    if (value != null && value.length() > 0 && loopValueElement.getAttribute("locale") == null) {
+                        values.add(JsonUtil.deserialize(value, CustomLinkConfiguration.class));
+                    }
+                }
+                final CustomLinkValue CustomLinkValue = new CustomLinkValue(values);
+                return CustomLinkValue;
+            }
+        };
+    }
+
+    public List<Element> toXmlValues(final String valueElementName) {
+        final List<Element> returnList = new ArrayList<>();
+        for (final CustomLinkConfiguration value : values) {
+            final Element valueElement = new Element(valueElementName);
+            valueElement.addContent(JsonUtil.serialize(value));
+            returnList.add(valueElement);
+        }
+        return returnList;
+    }
+
+    public List<CustomLinkConfiguration> toNativeObject() {
+        return Collections.unmodifiableList(values);
+    }
+
+    public List<String> validateValue(final PwmSetting pwmSetting) {
+        if (pwmSetting.isRequired()) {
+            if (values == null || values.size() < 1 || values.get(0) == null) {
+                return Collections.singletonList("required value missing");
+            }
+        }
+
+        final Set<String> seenNames = new HashSet<>();
+        for (final CustomLinkConfiguration loopConfig : values) {
+            if (seenNames.contains(loopConfig.getName().toLowerCase())) {
+                return Collections.singletonList("each form name must be unique: " + loopConfig.getName());
+            }
+            seenNames.add(loopConfig.getName().toLowerCase());
+        }
+
+        return Collections.emptyList();
+    }
+
+    public String toDebugString(final Locale locale) {
+        if (values != null && !values.isEmpty()) {
+            final StringBuilder sb = new StringBuilder();
+            for (final CustomLinkConfiguration formRow : values) {
+                sb.append("Link Name:").append(formRow.getName()).append("\n");
+                sb.append(" Type:").append(formRow.getType());
+                sb.append("\n");
+                sb.append(" Description:").append(JsonUtil.serializeMap(formRow.getLabels())).append("\n");
+                sb.append(" New Window:").append(formRow.isNewWindow()).append("\n");
+                sb.append(" Url:").append(formRow.getUrl()).append("\n");
+                if (formRow.getSelectOptions() != null && !formRow.getSelectOptions().isEmpty()) {
+                    sb.append(" Select Options: ").append(JsonUtil.serializeMap(formRow.getSelectOptions())).append("\n");
+                }
+
+            }
+            return sb.toString();
+        } else {
+            return "";
+        }
+    }
+
+}

+ 5 - 3
src/main/java/password/pwm/config/value/FormValue.java

@@ -24,7 +24,7 @@ package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
 import org.jdom2.Element;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.StoredValue;
@@ -148,9 +148,11 @@ public class FormValue extends AbstractValue implements StoredValue {
                 sb.append(" Required:").append(formRow.isRequired());
                 sb.append(" Confirm:").append(formRow.isConfirmationRequired());
                 sb.append(" Unique:").append(formRow.isUnique());
+                sb.append(" Multi-Value:").append(formRow.isMultivalue());
+                sb.append(" Source:").append(formRow.getSource());
                 sb.append("\n");
-                sb.append(" Label:").append(JsonUtil.serializeMap(formRow.getLabelLocaleMap())).append("\n");
-                sb.append(" Description:").append(JsonUtil.serializeMap(formRow.getLabelDescriptionLocaleMap())).append("\n");
+                sb.append(" Label:").append(JsonUtil.serializeMap(formRow.getLabels())).append("\n");
+                sb.append(" Description:").append(JsonUtil.serializeMap(formRow.getDescription())).append("\n");
                 if (formRow.getSelectOptions() != null && !formRow.getSelectOptions().isEmpty()) {
                     sb.append(" Select Options: ").append(JsonUtil.serializeMap(formRow.getSelectOptions())).append("\n");
                 }

+ 29 - 3
src/main/java/password/pwm/config/value/NamedSecretValue.java

@@ -25,7 +25,7 @@ package password.pwm.config.value;
 import com.google.gson.reflect.TypeToken;
 import org.jdom2.Element;
 import password.pwm.PwmConstants;
-import password.pwm.config.NamedSecretData;
+import password.pwm.config.value.data.NamedSecretData;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.error.ErrorInformation;
@@ -34,6 +34,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.SecureEngine;
@@ -176,12 +177,37 @@ public class NamedSecretValue implements StoredValue {
 
     @Override
     public String toDebugString(final Locale locale) {
-        return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT;
+        final StringBuilder sb = new StringBuilder();
+        for (final String name : values.keySet()) {
+            final NamedSecretData existingData = values.get(name);
+            sb.append("Named password '").append(name).append("' with usage for ");
+            sb.append(StringUtil.collectionToString(existingData.getUsage(), ","));
+            sb.append("\n");
+
+        }
+        return sb.toString();
     }
 
     @Override
     public Serializable toDebugJsonObject(final Locale locale) {
-        return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT;
+        if (values == null) {
+            return null;
+        }
+
+        try {
+            final LinkedHashMap<String,NamedSecretData> copiedValues = new LinkedHashMap<>();
+            for (final String name : values.keySet()) {
+                final NamedSecretData existingData = values.get(name);
+                final NamedSecretData newData = new NamedSecretData(
+                        PasswordData.forStringValue(PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT),
+                        existingData.getUsage()
+                );
+                copiedValues.put(name, newData);
+            }
+            return copiedValues;
+        } catch (PwmUnrecoverableException e) {
+            throw new IllegalStateException(e.getErrorInformation().toDebugStr());
+        }
     }
 
     public boolean requiresStoredUpdate()

+ 158 - 0
src/main/java/password/pwm/config/value/RemoteWebServiceValue.java

@@ -0,0 +1,158 @@
+/*
+ * 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.config.value;
+
+import com.google.gson.reflect.TypeToken;
+import org.jdom2.Element;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.StoredValue;
+import password.pwm.config.value.data.RemoteWebServiceConfiguration;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.util.java.JsonUtil;
+import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.X509Utils;
+
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class RemoteWebServiceValue extends AbstractValue implements StoredValue {
+    final List<RemoteWebServiceConfiguration> values;
+
+    public RemoteWebServiceValue(final List<RemoteWebServiceConfiguration> values) {
+        this.values = Collections.unmodifiableList(values);
+    }
+
+    public static StoredValueFactory factory()
+    {
+        return new StoredValueFactory() {
+            public RemoteWebServiceValue fromJson(final String input)
+            {
+                if (input == null) {
+                    return new RemoteWebServiceValue(Collections.emptyList());
+                } else {
+                    List<RemoteWebServiceConfiguration> srcList = JsonUtil.deserialize(input,
+                            new TypeToken<List<RemoteWebServiceConfiguration>>() {
+                            }
+                    );
+
+                    srcList = srcList == null ? Collections.emptyList() : srcList;
+                    srcList.removeIf(Objects::isNull);
+                    return new RemoteWebServiceValue(Collections.unmodifiableList(srcList));
+                }
+            }
+
+            public RemoteWebServiceValue fromXmlElement(
+                    final Element settingElement,
+                    final PwmSecurityKey input
+            )
+                    throws PwmOperationalException
+            {
+                final List valueElements = settingElement.getChildren("value");
+                final List<RemoteWebServiceConfiguration> values = new ArrayList<>();
+                for (final Object loopValue : valueElements) {
+                    final Element loopValueElement = (Element) loopValue;
+                    final String value = loopValueElement.getText();
+                    if (value != null && value.length() > 0) {
+                            values.add(JsonUtil.deserialize(value, RemoteWebServiceConfiguration.class));
+                    }
+                }
+                return new RemoteWebServiceValue(values);
+            }
+        };
+    }
+
+    public List<Element> toXmlValues(final String valueElementName) {
+        final List<Element> returnList = new ArrayList<>();
+        for (final RemoteWebServiceConfiguration value : values) {
+            final Element valueElement = new Element(valueElementName);
+            valueElement.addContent(JsonUtil.serialize(value));
+            returnList.add(valueElement);
+        }
+        return returnList;
+    }
+
+    public List<RemoteWebServiceConfiguration> toNativeObject() {
+        return Collections.unmodifiableList(values);
+    }
+
+    public List<String> validateValue(final PwmSetting pwmSetting) {
+        if (pwmSetting.isRequired()) {
+            if (values == null || values.size() < 1 || values.get(0) == null) {
+                return Collections.singletonList("required value missing");
+            }
+        }
+
+        final Set<String> seenNames = new HashSet<>();
+        for (final RemoteWebServiceConfiguration item : values) {
+            if (seenNames.contains(item.getName().toLowerCase())) {
+                return Collections.singletonList("each action name must be unique: " + item.getName().toLowerCase());
+            }
+            seenNames.add(item.getName().toLowerCase());
+        }
+
+
+        return Collections.emptyList();
+    }
+
+    public List<Map<String,Object>> toInfoMap() {
+        final String originalJson = JsonUtil.serializeCollection(values);
+        final List<Map<String,Object>> tempObj = JsonUtil.deserialize(originalJson, new TypeToken<List<Map<String,Object>>>() {
+        });
+        for (final Map<String,Object> mapObj : tempObj) {
+            final RemoteWebServiceConfiguration serviceConfig = forName((String)mapObj.get("name"));
+            if (serviceConfig != null && serviceConfig.getCertificates() != null) {
+                final List<Map<String,String>> certificateInfos = new ArrayList<>();
+                for (final X509Certificate certificate : serviceConfig.getCertificates()) {
+                    certificateInfos.add(X509Utils.makeDebugInfoMap(certificate, X509Utils.DebugInfoFlag.IncludeCertificateDetail));
+                }
+                mapObj.put("certificateInfos", certificateInfos);
+            }
+        }
+        return tempObj;
+    }
+
+
+    public RemoteWebServiceConfiguration forName(final String name) {
+        if (name==null) {
+            return null;
+        }
+        for (final RemoteWebServiceConfiguration config : values) {
+            if (name.equals(config.getName())) {
+                return config;
+            }
+        }
+        return null;
+    }
+
+    public String toDebugString(final Locale locale) {
+        return JsonUtil.serialize(this);
+    }
+
+}

+ 1 - 1
src/main/java/password/pwm/config/value/StringValue.java

@@ -24,7 +24,7 @@ package password.pwm.config.value;
 
 import org.jdom2.CDATA;
 import org.jdom2.Element;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingFlag;
 import password.pwm.config.StoredValue;

+ 1 - 1
src/main/java/password/pwm/config/value/UserPermissionValue.java

@@ -27,7 +27,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.i18n.Display;

+ 5 - 45
src/main/java/password/pwm/config/ActionConfiguration.java → src/main/java/password/pwm/config/value/data/ActionConfiguration.java

@@ -20,8 +20,10 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.config;
+package password.pwm.config.value.data;
 
+import lombok.Getter;
+import lombok.Setter;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -31,6 +33,8 @@ import java.io.Serializable;
 import java.security.cert.X509Certificate;
 import java.util.Map;
 
+@Getter
+@Setter
 public class ActionConfiguration implements Serializable {
 
     public enum Type { webservice, ldap }
@@ -55,50 +59,6 @@ public class ActionConfiguration implements Serializable {
     private String attributeName;
     private String attributeValue;
 
-    public String getName() {
-        return name;
-    }
-
-    public X509Certificate[] getCertificates() {
-        return certificates;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public Type getType() {
-        return type;
-    }
-
-    public WebMethod getMethod() {
-        return method;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    public LdapMethod getLdapMethod() {
-        return ldapMethod;
-    }
-
-    public Map<String, String> getHeaders() {
-        return headers;
-    }
-
-    public String getBody() {
-        return body;
-    }
-
-    public String getAttributeName() {
-        return attributeName;
-    }
-
-    public String getAttributeValue() {
-        return attributeValue;
-    }
-
     public static ActionConfiguration parseOldConfigString(final String value) {
         final String[] splitString = value.split("=");
         final String attributeName = splitString[0];

+ 4 - 29
src/main/java/password/pwm/config/ChallengeItemConfiguration.java → src/main/java/password/pwm/config/value/data/ChallengeItemConfiguration.java

@@ -20,10 +20,13 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.config;
+package password.pwm.config.value.data;
+
+import lombok.Getter;
 
 import java.io.Serializable;
 
+@Getter
 public class ChallengeItemConfiguration implements Serializable {
     private String text;
     private int minLength;
@@ -48,32 +51,4 @@ public class ChallengeItemConfiguration implements Serializable {
         this.maxLength = maximumLength;
         this.adminDefined = adminDefined;
     }
-
-    public String getText()
-    {
-        return text;
-    }
-
-    public int getMinLength()
-    {
-        return minLength;
-    }
-
-    public int getMaxLength()
-    {
-        return maxLength;
-    }
-
-    public boolean isAdminDefined()
-    {
-        return adminDefined;
-    }
-
-    public int getMaxQuestionCharsInAnswer() {
-        return maxQuestionCharsInAnswer;
-    }
-
-    public boolean isEnforceWordlist() {
-        return enforceWordlist;
-    }
 }

+ 44 - 87
src/main/java/password/pwm/config/FormConfiguration.java → src/main/java/password/pwm/config/value/data/FormConfiguration.java

@@ -20,10 +20,12 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.config;
+package password.pwm.config.value.data;
 
+import lombok.Getter;
 import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
+import password.pwm.config.Configuration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
@@ -50,15 +52,38 @@ import java.util.regex.PatternSyntaxException;
 /**
  * @author Jason D. Rivard
  */
+@Getter
 public class FormConfiguration implements Serializable {
-// ------------------------------ FIELDS ------------------------------
 
-    public enum Type {text, email, number, password, random, tel, hidden, date, datetime, time, week, month, url, select, userDN, checkbox}
+    public enum Type {
+        text,
+        email,
+        number,
+        password,
+        random,
+        tel,
+        hidden,
+        date,
+        datetime,
+        time,
+        week,
+        month,
+        url,
+        select,
+        userDN,
+        checkbox,
+    }
+
+    public enum Source {
+        ldap,
+        remote,
+    }
 
-    private String name;
+    private String name = "";
     private int minimumLength;
     private int maximumLength;
     private Type type = Type.text;
+    private Source source = Source.ldap;
     private boolean required;
     private boolean confirmationRequired;
     private boolean readonly;
@@ -67,12 +92,23 @@ public class FormConfiguration implements Serializable {
     private Map<String,String> labels = Collections.singletonMap("", "");
     private Map<String,String> regexErrors = Collections.singletonMap("","");
     private Map<String,String> description = Collections.singletonMap("","");
-    private String regex;
-    private String placeholder;
-    private String javascript;
+    private String regex = "";
+    private String placeholder = "";
+    private String javascript = "";
     private Map<String,String> selectOptions = Collections.emptyMap();
 
-// -------------------------- STATIC METHODS --------------------------
+
+    public String getRegexError(final Locale locale) {
+        return LocaleHelper.resolveStringKeyLocaleMap(locale, regexErrors);
+    }
+
+    public String getLabel(final Locale locale) {
+        return LocaleHelper.resolveStringKeyLocaleMap(locale, labels);
+    }
+
+    public String getDescription(final Locale locale) {
+        return LocaleHelper.resolveStringKeyLocaleMap(locale, description);
+    }
 
     public static FormConfiguration parseOldConfigString(final String config)
             throws PwmOperationalException
@@ -151,88 +187,11 @@ public class FormConfiguration implements Serializable {
         }
     }
 
-// --------------------------- CONSTRUCTORS ---------------------------
-
     public FormConfiguration() {
         labels = Collections.singletonMap("","");
         regexErrors = Collections.singletonMap("","");
     }
 
-// --------------------- GETTER / SETTER METHODS ---------------------
-
-    public String getName() {
-        return name;
-    }
-
-    public String getLabel(final Locale locale) {
-        return LocaleHelper.resolveStringKeyLocaleMap(locale, labels);
-    }
-
-    public Map<String,String> getLabelLocaleMap() {
-        return Collections.unmodifiableMap(this.labels);
-    }
-
-    public String getRegexError(final Locale locale) {
-        return LocaleHelper.resolveStringKeyLocaleMap(locale, regexErrors);
-    }
-
-    public String getDescription(final Locale locale) {
-        return LocaleHelper.resolveStringKeyLocaleMap(locale, description);
-    }
-
-    public Map<String,String> getLabelDescriptionLocaleMap() {
-        return Collections.unmodifiableMap(this.description);
-    }
-
-    public int getMaximumLength() {
-        return maximumLength;
-    }
-
-    public int getMinimumLength() {
-        return minimumLength;
-    }
-
-    public Type getType() {
-        return type;
-    }
-
-    public boolean isConfirmationRequired() {
-        return confirmationRequired;
-    }
-
-    public boolean isRequired() {
-        return required;
-    }
-
-    public boolean isReadonly() {
-        return readonly;
-    }
-
-    public boolean isUnique() {
-        return unique;
-    }
-
-    public boolean isMultivalue() {
-        return multivalue;
-    }
-
-    public String getRegex() {
-        return regex;
-    }
-
-    public String getPlaceholder() {
-        return placeholder;
-    }
-
-    public String getJavascript() {
-        return javascript;
-    }
-
-    public Map<String,String> getSelectOptions() {
-        return Collections.unmodifiableMap(selectOptions);
-    }
-
-// ------------------------ CANONICAL METHODS ------------------------
 
     public boolean equals(final Object o) {
         if (this == o) {
@@ -262,8 +221,6 @@ public class FormConfiguration implements Serializable {
 
 
 
-// -------------------------- OTHER METHODS --------------------------
-
     public void checkValue(final Configuration config, final String value, final Locale locale)
             throws PwmDataValidationException, PwmUnrecoverableException {
 

+ 1 - 1
src/main/java/password/pwm/config/NamedSecretData.java → src/main/java/password/pwm/config/value/data/NamedSecretData.java

@@ -20,7 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.config;
+package password.pwm.config.value.data;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;

+ 50 - 0
src/main/java/password/pwm/config/value/data/RemoteWebServiceConfiguration.java

@@ -0,0 +1,50 @@
+/*
+ * 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.config.value.data;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+@Getter
+@Setter
+public class RemoteWebServiceConfiguration implements Serializable {
+
+    public enum WebMethod {
+        delete,
+        get,
+        post,
+        put,
+        patch
+    }
+
+    private String name;
+    private WebMethod method = WebMethod.get;
+    private Map<String,String> headers;
+    private String url;
+    private String body;
+    private X509Certificate[] certificates;
+}

+ 5 - 25
src/main/java/password/pwm/config/ShortcutItem.java → src/main/java/password/pwm/config/value/data/ShortcutItem.java

@@ -20,13 +20,17 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.config;
+package password.pwm.config.value.data;
 
+import lombok.AllArgsConstructor;
+import lombok.Getter;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.Serializable;
 import java.net.URI;
 
+@Getter
+@AllArgsConstructor
 public class ShortcutItem implements Serializable {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(ShortcutItem.class);
@@ -36,30 +40,6 @@ public class ShortcutItem implements Serializable {
     private final String ldapQuery;
     private final String description;
 
-    public ShortcutItem(final String label, final URI shortcutURI, final String ldapQuery, final String description) {
-        this.ldapQuery = ldapQuery;
-        this.shortcutURI = shortcutURI;
-        this.label = label;
-        this.description = description;
-    }
-
-    public String getLdapQuery() {
-        return ldapQuery;
-    }
-
-    public URI getShortcutURI() {
-        return shortcutURI;
-    }
-
-    public String getLabel() {
-        return label;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-
     public String toString() {
         return "ShortcutItem{" +
                 "label='" + label + '\'' +

+ 1 - 1
src/main/java/password/pwm/config/UserPermission.java → src/main/java/password/pwm/config/value/data/UserPermission.java

@@ -20,7 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.config;
+package password.pwm.config.value.data;
 
 import password.pwm.PwmConstants;
 

+ 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),

+ 1 - 1
src/main/java/password/pwm/health/CertificateChecker.java

@@ -25,7 +25,7 @@ package password.pwm.health;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;

+ 10 - 4
src/main/java/password/pwm/health/LDAPStatusChecker.java

@@ -45,7 +45,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingFlag;
 import password.pwm.config.PwmSettingSyntax;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordRule;
@@ -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;
     }

+ 4 - 0
src/main/java/password/pwm/http/ContextManager.java

@@ -464,6 +464,10 @@ public class ContextManager implements Serializable {
         }
     }
 
+    public String getServerInfo() {
+        return servletContext.getServerInfo();
+    }
+
     public String getContextPath() {
         return contextPath;
     }

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

@@ -35,7 +35,7 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.ldap.UserInfo;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -142,6 +142,9 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         if (isFlag(PwmRequestFlag.INCLUDE_CONFIG_CSS)) {
             return PwmConstants.DEFAULT_LOCALE;
         }
+        if (!getURL().isLocalizable()) {
+            return PwmConstants.DEFAULT_LOCALE;
+        }
         return pwmSession.getSessionStateBean().getLocale();
     }
 
@@ -530,6 +533,13 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         this.setAttribute(PwmRequestAttribute.FormData, formDataMapValue);
         this.setAttribute(PwmRequestAttribute.FormReadOnly, readOnly);
         this.setAttribute(PwmRequestAttribute.FormShowPasswordFields, showPasswordFields);
+        this.setAttribute(PwmRequestAttribute.FormMobileDevices, formDataMapValue);
+        this.setAttribute(PwmRequestAttribute.FormCustomLinks, new ArrayList<>(formConfiguration));
+    }
+
+    public void addFormInfoToRequestAttr(
+            final List<FormConfiguration> FormCustomLinks) {
+        this.setAttribute(PwmRequestAttribute.FormCustomLinks, new ArrayList<>(FormCustomLinks));
     }
 
     public void invalidateSession() {

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

@@ -42,6 +42,8 @@ public enum PwmRequestAttribute {
     FormReadOnly,
     FormShowPasswordFields,
     FormData,
+    FormMobileDevices,
+    FormCustomLinks,
 
     SetupResponses_ResponseInfo,
 
@@ -80,6 +82,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()) {

+ 7 - 0
src/main/java/password/pwm/http/PwmURL.java

@@ -187,6 +187,13 @@ public class PwmURL {
         return isPwmServletURL(PwmServletDefinition.UpdateProfile);
     }
 
+    public boolean isLocalizable() {
+        return !isConfigGuideURL()
+                && !isAdminUrl()
+                && !isReferenceURL()
+                && !isConfigManagerURL();
+    }
+
     public String toString() {
         return uri.toString();
     }

+ 1 - 1
src/main/java/password/pwm/http/SessionManager.java

@@ -31,7 +31,7 @@ import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.ldap.UserInfo;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.profile.DeleteAccountProfile;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.Profile;

+ 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;
     }

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

@@ -26,7 +26,7 @@ import com.google.gson.annotations.SerializedName;
 import com.novell.ldapchai.cr.ChallengeSet;
 import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.SessionBeanMode;

+ 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 - 1
src/main/java/password/pwm/http/bean/ShortcutsBean.java

@@ -22,7 +22,7 @@
 
 package password.pwm.http.bean;
 
-import password.pwm.config.ShortcutItem;
+import password.pwm.config.value.data.ShortcutItem;
 import password.pwm.config.option.SessionBeanMode;
 
 import java.util.Arrays;

+ 45 - 41
src/main/java/password/pwm/http/filter/AuthenticationFilter.java

@@ -30,7 +30,6 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmHttpFilterAuthenticationProvider;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.UserIdentity;
-import password.pwm.ldap.UserInfo;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -50,6 +49,7 @@ import password.pwm.http.servlet.oauth.OAuthMachine;
 import password.pwm.http.servlet.oauth.OAuthSettings;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.PasswordChangeProgressChecker;
+import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
@@ -65,7 +65,6 @@ import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.io.Serializable;
 import java.time.Instant;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -291,42 +290,53 @@ public class AuthenticationFilter extends AbstractPwmFilter {
         LoginServlet.redirectToLoginServlet(pwmRequest);
     }
 
-    public static ProcessStatus attemptAuthenticationMethods(final PwmRequest pwmRequest) throws IOException, ServletException {
-        final Set<AuthenticationMethod> authenticationMethods = new HashSet<>(Arrays.asList(AuthenticationMethod.values()));
-        {
-            final String casUrl = pwmRequest.getConfig().readSettingAsString(PwmSetting.CAS_CLEAR_PASS_URL);
-            if (casUrl == null || casUrl.trim().isEmpty()) {
-                authenticationMethods.remove(AuthenticationMethod.CAS);
-            }
-        }
-        for (final AuthenticationMethod authenticationMethod : authenticationMethods) {
-            if (!pwmRequest.isAuthenticated()) {
-                try {
-                    final Class<? extends PwmHttpFilterAuthenticationProvider> clazz = authenticationMethod.getImplementationClass();
-                    final PwmHttpFilterAuthenticationProvider filterAuthenticationProvider = clazz.newInstance();
-                    filterAuthenticationProvider.attemptAuthentication(pwmRequest);
-
-                    if (pwmRequest.isAuthenticated()) {
-                        LOGGER.trace(pwmRequest, "authentication provided by method " + clazz.getName());
-                    }
+    private static final Set<AuthenticationMethod> IGNORED_AUTH_METHODS = new HashSet<>();
 
-                    if (filterAuthenticationProvider.hasRedirectedResponse()) {
-                        LOGGER.trace(pwmRequest, "authentication provider " + clazz.getName() + " has issued a redirect, halting authentication process");
-                        return ProcessStatus.Halt;
-                    }
+    private static ProcessStatus attemptAuthenticationMethods(final PwmRequest pwmRequest) throws IOException, ServletException {
+        if (pwmRequest.isAuthenticated()) {
+            return ProcessStatus.Continue;
+        }
 
+        for (final AuthenticationMethod authenticationMethod : AuthenticationMethod.values()) {
+            if (!IGNORED_AUTH_METHODS.contains(authenticationMethod)) {
+                PwmHttpFilterAuthenticationProvider filterAuthenticationProvider = null;
+                try {
+                    final String className = authenticationMethod.getClassName();
+                    final Class clazz = Class.forName(className);
+                    final Object newInstance = clazz.newInstance();
+                    filterAuthenticationProvider = (PwmHttpFilterAuthenticationProvider)newInstance;
                 } catch (Exception e) {
-                    final ErrorInformation errorInformation;
-                    if (e instanceof PwmException) {
-                        final String erorrMessage = "error during " + authenticationMethod + " authentication attempt: " + e.getMessage();
-                        errorInformation = new ErrorInformation(((PwmException) e).getError(), erorrMessage);
-                    } else {
-                        errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage());
+                    LOGGER.trace("could not load authentication class '" + authenticationMethod + "', will ignore");
+                    IGNORED_AUTH_METHODS.add(authenticationMethod);
+                }
 
+                if (filterAuthenticationProvider != null) {
+                    try {
+                        filterAuthenticationProvider.attemptAuthentication(pwmRequest);
+
+                        if (pwmRequest.isAuthenticated()) {
+                            LOGGER.trace(pwmRequest, "authentication provided by method " + authenticationMethod.name());
+                        }
+
+                        if (filterAuthenticationProvider.hasRedirectedResponse()) {
+                            LOGGER.trace(pwmRequest, "authentication provider " + authenticationMethod.name()
+                                    + " has issued a redirect, halting authentication process");
+                            return ProcessStatus.Halt;
+                        }
+
+                    } catch (Exception e) {
+                        final ErrorInformation errorInformation;
+                        if (e instanceof PwmException) {
+                            final String errorMsg = "error during " + authenticationMethod + " authentication attempt: " + e.getMessage();
+                            errorInformation = new ErrorInformation(((PwmException) e).getError(), errorMsg);
+                        } else {
+                            errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage());
+
+                        }
+                        LOGGER.error(pwmRequest, errorInformation);
+                        pwmRequest.respondWithError(errorInformation);
+                        return ProcessStatus.Halt;
                     }
-                    LOGGER.error(pwmRequest, errorInformation);
-                    pwmRequest.respondWithError(errorInformation);
-                    return ProcessStatus.Halt;
                 }
             }
         }
@@ -440,14 +450,8 @@ public class AuthenticationFilter extends AbstractPwmFilter {
             this.className = className;
         }
 
-        public Class<? extends PwmHttpFilterAuthenticationProvider> getImplementationClass() throws PwmUnrecoverableException {
-            try {
-                return (Class<? extends PwmHttpFilterAuthenticationProvider>) Class.forName(className);
-            } catch (ClassNotFoundException | ClassCastException e) {
-                final String errorMsg = "error loading authentication method: " + this.getImplementationClass() + ", error: " + e.getMessage();
-                LOGGER.error(errorMsg,e);
-                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMsg));
-            }
+        public String getClassName() {
+            return className;
         }
     }
 

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

@@ -220,7 +220,7 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
 
             case ERROR_UNKNOWN:
             default:
-                LOGGER.fatal(e.getErrorInformation().toDebugStr());
+                LOGGER.fatal(pwmSession, "unexpected error: " + e.getErrorInformation().toDebugStr());
                 try { // try to update stats
                     if (pwmSession != null) {
                         pwmApplication.getStatisticsManager().incrementValue(Statistic.PWM_UNKNOWN_ERRORS);

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

@@ -24,7 +24,7 @@ package password.pwm.http.servlet;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmConstants;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;

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

@@ -34,12 +34,12 @@ import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.error.ErrorInformation;

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

@@ -29,7 +29,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.DeleteAccountProfile;
@@ -208,7 +208,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet {
                 try {
                     actionExecutor.executeActions(actions, pwmRequest.getPwmSession());
                 } catch (PwmOperationalException e) {
-                    LOGGER.error("error during user delete action execution: ", e);
+                    LOGGER.error("error during user delete action execution: " + e.getMessage());
                     throw new PwmUnrecoverableException(e.getErrorInformation(),e.getCause());
                 }
             }

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

@@ -30,7 +30,7 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.MessageSendMethod;

+ 3 - 4
src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java

@@ -33,9 +33,9 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.PwmPasswordPolicy;
@@ -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);

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

@@ -27,7 +27,7 @@ import com.novell.ldapchai.util.StringHelper;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.ShortcutItem;
+import password.pwm.config.value.data.ShortcutItem;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;

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

@@ -30,9 +30,9 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.TokenVerificationProgress;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.TokenStorageMethod;
@@ -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()])
         );
     }
 
@@ -565,6 +571,8 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
         final List<FormConfiguration> form = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM);
         final Map<FormConfiguration,String> formValueMap = formMapFromBean(updateAttributesProfile, updateProfileBean);
         pwmRequest.addFormInfoToRequestAttr(form, formValueMap, false, false);
+        final List<FormConfiguration> links = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_CUSTOMLINKS);
+        pwmRequest.addFormInfoToRequestAttr(links);
         pwmRequest.forwardToJsp(JspUrl.UPDATE_ATTRIBUTES);
     }
 
@@ -574,6 +582,8 @@ public class UpdateProfileServlet extends ControlledPwmServlet {
         final List<FormConfiguration> form = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_FORM);
         final Map<FormConfiguration,String> formValueMap = formMapFromBean(updateAttributesProfile, updateProfileBean);
         pwmRequest.addFormInfoToRequestAttr(form, formValueMap, true, false);
+        final List<FormConfiguration> links = updateAttributesProfile.readSettingAsForm(PwmSetting.UPDATE_PROFILE_CUSTOMLINKS);
+        pwmRequest.addFormInfoToRequestAttr(links);
         pwmRequest.forwardToJsp(JspUrl.UPDATE_ATTRIBUTES_CONFIRM);
     }
 

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

@@ -29,7 +29,7 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.profile.PwmPasswordPolicy;

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

@@ -35,7 +35,7 @@ import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.ldap.UserInfo;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.RequireCurrentPasswordMode;

+ 5 - 0
src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -42,6 +42,7 @@ import password.pwm.config.stored.ValueMetaData;
 import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.PrivateKeyValue;
+import password.pwm.config.value.RemoteWebServiceValue;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.ErrorInformation;
@@ -361,6 +362,10 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
                     returnValue = ((ActionValue)storedConfig.readSetting(theSetting, profile)).toInfoMap();
                     break;
 
+                case REMOTE_WEB_SERVICE:
+                    returnValue = ((RemoteWebServiceValue)storedConfig.readSetting(theSetting, profile)).toInfoMap();
+                    break;
+
                 case FILE:
                     returnValue = ((FileValue) storedConfig.readSetting(theSetting, profile)).toInfoMap();
                     break;

+ 34 - 63
src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java

@@ -25,8 +25,9 @@ package password.pwm.http.servlet.configguide;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.StoredValue;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.FileValue;
 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);
         }
     }

+ 20 - 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,9 @@ import java.util.Set;
 
 public enum GuideStep {
     START(null),
+    EULA(EulaVisibilityCheck.class),
+    MENU(null),
+    TELEMETRY(TelemetryVisibilityCheck.class),
     TEMPLATE(null),
     LDAP_SERVER(null),
     LDAP_CERT(null),
@@ -107,4 +113,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();
+        }
+    }
 }

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

@@ -24,7 +24,7 @@ package password.pwm.http.servlet.configmanager;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmConstants;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;

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

@@ -38,9 +38,9 @@ import password.pwm.bean.PasswordStatus;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.ldap.UserInfo;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;

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

@@ -34,7 +34,7 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.option.MessageSendMethod;

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

@@ -27,7 +27,7 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.bean.UserIdentity;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoBean;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.HelpdeskProfile;

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

@@ -34,9 +34,9 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.HelpdeskClearResponseMode;
 import password.pwm.config.option.HelpdeskUIMode;

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

@@ -22,7 +22,7 @@
 
 package password.pwm.http.servlet.newuser;
 
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.NewUserProfile;
@@ -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()));
                 }
             }

+ 63 - 14
src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -27,7 +27,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.TokenVerificationProgress;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.NewUserProfile;
@@ -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
     {

+ 13 - 1
src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -37,7 +37,7 @@ import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.TokenVerificationProgress;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.ActionConfiguration;
+import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.TokenStorageMethod;
@@ -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);
+    }
 }

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

@@ -22,7 +22,7 @@
 
 package password.pwm.http.servlet.peoplesearch;
 
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 
 import java.io.Serializable;
 import java.util.Collection;

+ 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;
     }
 }

+ 21 - 10
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java

@@ -32,9 +32,9 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -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

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

@@ -24,7 +24,7 @@ package password.pwm.http.servlet.peoplesearch;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -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();
         }
     }
 

+ 2 - 2
src/main/java/password/pwm/ldap/LdapOperationsHelper.java

@@ -39,7 +39,7 @@ import password.pwm.PwmApplication;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.error.ErrorInformation;
@@ -215,7 +215,7 @@ public class LdapOperationsHelper {
      *
      * @param pwmSession       for looking up session info
      * @param theUser          User to write to
-     * @param formValues       A map with {@link password.pwm.config.FormConfiguration} keys and String values.
+     * @param formValues       A map with {@link FormConfiguration} keys and String values.
      * @throws ChaiUnavailableException if the directory is unavailable
      * @throws PwmOperationalException if their is an unexpected ldap problem
      */

+ 1 - 1
src/main/java/password/pwm/ldap/LdapPermissionTester.java

@@ -30,7 +30,7 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;

+ 2 - 2
src/main/java/password/pwm/ldap/UserInfoReader.java

@@ -34,10 +34,10 @@ import password.pwm.bean.ResponseInfoBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.UserPermission;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.option.ForceSetupPolicy;
 import password.pwm.config.profile.ChallengeProfile;

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

@@ -25,7 +25,7 @@ package password.pwm.ldap.search;
 import com.novell.ldapchai.provider.ChaiProvider;
 import lombok.Builder;
 import lombok.Getter;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 
 import java.io.Serializable;
 import java.util.List;

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

@@ -36,7 +36,7 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DuplicateMode;
 import password.pwm.config.profile.LdapProfile;
@@ -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(

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

@@ -24,7 +24,7 @@ package password.pwm.ldap.search;
 
 import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.PwmUnrecoverableException;
 
 import java.io.Serializable;

+ 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)) {

+ 1 - 1
src/main/java/password/pwm/svc/intruder/IntruderManager.java

@@ -28,7 +28,7 @@ import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
-import password.pwm.config.FormConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.IntruderStorageMethod;

+ 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;
+}

Some files were not shown because too many files changed in this diff