ソースを参照

Merge remote-tracking branch 'pwm/master'

rkeil 8 年 前
コミット
a4ce0b99d4
67 ファイル変更1746 行追加588 行削除
  1. 6 0
      src/main/java/password/pwm/AppProperty.java
  2. 6 1
      src/main/java/password/pwm/PwmApplication.java
  3. 1 0
      src/main/java/password/pwm/bean/SessionLabel.java
  4. 30 4
      src/main/java/password/pwm/config/Configuration.java
  5. 6 0
      src/main/java/password/pwm/config/FormConfiguration.java
  6. 1 1
      src/main/java/password/pwm/config/FormUtility.java
  7. 37 0
      src/main/java/password/pwm/config/NamedSecretData.java
  8. 1 4
      src/main/java/password/pwm/config/PwmSetting.java
  9. 2 0
      src/main/java/password/pwm/config/PwmSettingSyntax.java
  10. 27 0
      src/main/java/password/pwm/config/option/WebServiceUsage.java
  11. 3 3
      src/main/java/password/pwm/config/profile/NewUserProfile.java
  12. 4 0
      src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  13. 197 0
      src/main/java/password/pwm/config/value/NamedSecretValue.java
  14. 86 19
      src/main/java/password/pwm/http/IdleTimeoutCalculator.java
  15. 1 0
      src/main/java/password/pwm/http/PwmRequestAttribute.java
  16. 17 3
      src/main/java/password/pwm/http/PwmSessionWrapper.java
  17. 17 37
      src/main/java/password/pwm/http/bean/NewUserBean.java
  18. 6 26
      src/main/java/password/pwm/http/bean/PwmSessionBean.java
  19. 4 7
      src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  20. 71 0
      src/main/java/password/pwm/http/servlet/newuser/NewUserForm.java
  21. 66 94
      src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java
  22. 37 18
      src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  23. 14 7
      src/main/java/password/pwm/http/servlet/newuser/NewUserTokenData.java
  24. 20 9
      src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  25. 2 2
      src/main/java/password/pwm/http/state/SessionStateService.java
  26. 4 3
      src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java
  27. 4 1
      src/main/java/password/pwm/svc/PwmServiceManager.java
  28. 35 0
      src/main/java/password/pwm/svc/cluster/ClusterProvider.java
  29. 106 0
      src/main/java/password/pwm/svc/cluster/ClusterService.java
  30. 240 0
      src/main/java/password/pwm/svc/cluster/DatabaseClusterProvider.java
  31. 50 0
      src/main/java/password/pwm/svc/cluster/DatabaseClusterSettings.java
  32. 54 0
      src/main/java/password/pwm/svc/cluster/DatabaseStoredNodeData.java
  33. 18 13
      src/main/java/password/pwm/svc/cluster/NodeInfo.java
  34. 4 13
      src/main/java/password/pwm/svc/event/AuditService.java
  35. 1 1
      src/main/java/password/pwm/svc/event/SyslogAuditService.java
  36. 6 6
      src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java
  37. 9 8
      src/main/java/password/pwm/svc/wordlist/Populator.java
  38. 7 78
      src/main/java/password/pwm/svc/wordlist/StoredWordlistDataBean.java
  39. 10 2
      src/main/java/password/pwm/util/BasicAuthInfo.java
  40. 97 102
      src/main/java/password/pwm/util/RandomPasswordGenerator.java
  41. 2 2
      src/main/java/password/pwm/util/cli/MainClass.java
  42. 19 10
      src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java
  43. 1 0
      src/main/java/password/pwm/util/db/DatabaseTable.java
  44. 2 1
      src/main/java/password/pwm/util/java/JavaHelper.java
  45. 3 3
      src/main/java/password/pwm/util/java/TimeDuration.java
  46. 3 2
      src/main/java/password/pwm/util/localdb/Derby_LocalDB.java
  47. 8 40
      src/main/java/password/pwm/ws/server/RestRequestBean.java
  48. 1 1
      src/main/java/password/pwm/ws/server/RestResultBean.java
  49. 7 20
      src/main/java/password/pwm/ws/server/RestServerHelper.java
  50. 89 0
      src/main/java/password/pwm/ws/server/StandaloneRestHelper.java
  51. 37 0
      src/main/java/password/pwm/ws/server/StandaloneRestRequestBean.java
  52. 1 1
      src/main/java/password/pwm/ws/server/rest/RestAppDataServer.java
  53. 1 1
      src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java
  54. 10 8
      src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java
  55. 15 14
      src/main/java/password/pwm/ws/server/rest/RestSigningServer.java
  56. 1 1
      src/main/java/password/pwm/ws/server/rest/RestStatusServer.java
  57. 1 1
      src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java
  58. 1 1
      src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java
  59. 7 1
      src/main/resources/password/pwm/AppProperty.properties
  60. 6 9
      src/main/resources/password/pwm/config/PwmSetting.xml
  61. 1 1
      src/main/resources/password/pwm/i18n/Admin.properties
  62. 2 4
      src/main/resources/password/pwm/i18n/PwmSetting.properties
  63. 61 4
      src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp
  64. 4 0
      src/main/webapp/WEB-INF/jsp/fragment/form.jsp
  65. 144 1
      src/main/webapp/public/resources/js/configeditor-settings.js
  66. 4 0
      src/main/webapp/public/resources/js/configeditor.js
  67. 8 0
      src/main/webapp/public/resources/js/main.js

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

@@ -69,6 +69,10 @@ public enum     AppProperty {
     CONFIG_GUIDE_IDLE_TIMEOUT                       ("configGuide.idleTimeoutSeconds"),
     CONFIG_GUIDE_IDLE_TIMEOUT                       ("configGuide.idleTimeoutSeconds"),
     CONFIG_MANAGER_ZIPDEBUG_MAXLOGLINES             ("configManager.zipDebug.maxLogLines"),
     CONFIG_MANAGER_ZIPDEBUG_MAXLOGLINES             ("configManager.zipDebug.maxLogLines"),
     CONFIG_MANAGER_ZIPDEBUG_MAXLOGSECONDS           ("configManager.zipDebug.maxLogSeconds"),
     CONFIG_MANAGER_ZIPDEBUG_MAXLOGSECONDS           ("configManager.zipDebug.maxLogSeconds"),
+    CLUSTER_DB_ENABLE                               ("cluster.db.enable"),
+    CLUSTER_DB_HEARTBEAT_SECONDS                    ("cluster.db.heartbeatSeconds"),
+    CLUSTER_DB_NODE_TIMEOUT_SECONDS                 ("cluster.db.nodeTimeoutSeconds"),
+    CLUSTER_DB_NODE_PURGE_SECONDS                   ("cluster.db.nodePurgeSeconds"),
     DB_JDBC_LOAD_STRATEGY                           ("db.jdbcLoadStrategy"),
     DB_JDBC_LOAD_STRATEGY                           ("db.jdbcLoadStrategy"),
     DB_CONNECTIONS_MAX                              ("db.connections.max"),
     DB_CONNECTIONS_MAX                              ("db.connections.max"),
     DB_CONNECTIONS_TIMEOUT_MS                       ("db.connections.timeoutMs"),
     DB_CONNECTIONS_TIMEOUT_MS                       ("db.connections.timeoutMs"),
@@ -141,6 +145,7 @@ public enum     AppProperty {
     LOCALDB_AGGRESSIVE_COMPACT_ENABLED              ("localdb.aggressiveCompact.enabled"),
     LOCALDB_AGGRESSIVE_COMPACT_ENABLED              ("localdb.aggressiveCompact.enabled"),
     LOCALDB_IMPLEMENTATION                          ("localdb.implementation"),
     LOCALDB_IMPLEMENTATION                          ("localdb.implementation"),
     LOCALDB_INIT_STRING                             ("localdb.initParameters"),
     LOCALDB_INIT_STRING                             ("localdb.initParameters"),
+    LOCALDB_LOCATION                                ("localdb.location"),
     LOCALDB_LOGWRITER_BUFFER_SIZE                   ("localdb.logWriter.bufferSize"),
     LOCALDB_LOGWRITER_BUFFER_SIZE                   ("localdb.logWriter.bufferSize"),
     LOCALDB_LOGWRITER_MAX_BUFFER_WAIT_MS            ("localdb.logWriter.maxBufferWaitMs"),
     LOCALDB_LOGWRITER_MAX_BUFFER_WAIT_MS            ("localdb.logWriter.maxBufferWaitMs"),
     LOCALDB_LOGWRITER_MAX_TRIM_SIZE                 ("localdb.logWriter.maxTrimSize"),
     LOCALDB_LOGWRITER_MAX_TRIM_SIZE                 ("localdb.logWriter.maxTrimSize"),
@@ -290,6 +295,7 @@ public enum     AppProperty {
     WORDLIST_CHAR_LENGTH_MAX                        ("wordlist.maxCharLength"),
     WORDLIST_CHAR_LENGTH_MAX                        ("wordlist.maxCharLength"),
     WORDLIST_CHAR_LENGTH_MIN                        ("wordlist.minCharLength"),
     WORDLIST_CHAR_LENGTH_MIN                        ("wordlist.minCharLength"),
     WS_REST_CLIENT_PWRULE_HALTONERROR               ("ws.restClient.pwRule.haltOnError"),
     WS_REST_CLIENT_PWRULE_HALTONERROR               ("ws.restClient.pwRule.haltOnError"),
+    WS_REST_SERVER_SIGNING_FORM_TIMEOUT_SECONDS     ("ws.restServer.signing.form.timeoutSeconds"),
     ALLOW_MACRO_IN_REGEX_SETTING                    ("password.policy.allowMacroInRegexSetting"),
     ALLOW_MACRO_IN_REGEX_SETTING                    ("password.policy.allowMacroInRegexSetting"),
 
 
     ;
     ;

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

@@ -42,6 +42,7 @@ import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmServiceManager;
 import password.pwm.svc.PwmServiceManager;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.cache.CacheService;
+import password.pwm.svc.cluster.ClusterService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditService;
 import password.pwm.svc.event.AuditService;
@@ -503,6 +504,10 @@ public class PwmApplication {
         return (VersionChecker)pwmServiceManager.getService(VersionChecker.class);
         return (VersionChecker)pwmServiceManager.getService(VersionChecker.class);
     }
     }
 
 
+    public ClusterService getClusterService() {
+        return (ClusterService) pwmServiceManager.getService(ClusterService.class);
+    }
+
     public ErrorInformation getLastLocalDBFailure() {
     public ErrorInformation getLastLocalDBFailure() {
         return lastLocalDBFailure;
         return lastLocalDBFailure;
     }
     }
@@ -697,7 +702,7 @@ public class PwmApplication {
             final File databaseDirectory;
             final File databaseDirectory;
             // see if META-INF isn't already there, then use WEB-INF.
             // see if META-INF isn't already there, then use WEB-INF.
             try {
             try {
-                final String localDBLocationSetting = pwmApplication.getConfig().readSettingAsString(PwmSetting.PWMDB_LOCATION);
+                final String localDBLocationSetting = pwmApplication.getConfig().readAppProperty(AppProperty.LOCALDB_LOCATION);
                 databaseDirectory = FileSystemUtility.figureFilepath(localDBLocationSetting, pwmApplication.pwmEnvironment.getApplicationPath());
                 databaseDirectory = FileSystemUtility.figureFilepath(localDBLocationSetting, pwmApplication.pwmEnvironment.getApplicationPath());
             } catch (Exception e) {
             } catch (Exception e) {
                 pwmApplication.lastLocalDBFailure = new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,"error locating configured LocalDB directory: " + e.getMessage());
                 pwmApplication.lastLocalDBFailure = new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,"error locating configured LocalDB directory: " + e.getMessage());

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

@@ -37,6 +37,7 @@ public class SessionLabel implements Serializable {
     public static final SessionLabel CLI_SESSION_LABEL= new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"cli",null,null);
     public static final SessionLabel CLI_SESSION_LABEL= new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"cli",null,null);
     public static final SessionLabel HEALTH_SESSION_LABEL = new SessionLabel(SESSION_LABEL_SESSION_ID ,null,"health",null,null);
     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 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);
 
 
     private final String sessionID;
     private final String sessionID;
     private final UserIdentity userIdentity;
     private final UserIdentity userIdentity;

+ 30 - 4
src/main/java/password/pwm/config/Configuration.java

@@ -51,6 +51,7 @@ import password.pwm.config.value.FileValue;
 import password.pwm.config.value.FormValue;
 import password.pwm.config.value.FormValue;
 import password.pwm.config.value.LocalizedStringArrayValue;
 import password.pwm.config.value.LocalizedStringArrayValue;
 import password.pwm.config.value.LocalizedStringValue;
 import password.pwm.config.value.LocalizedStringValue;
+import password.pwm.config.value.NamedSecretValue;
 import password.pwm.config.value.NumericValue;
 import password.pwm.config.value.NumericValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringArrayValue;
@@ -96,6 +97,9 @@ public class Configuration implements Serializable, SettingReader {
 
 
     private DataCache dataCache = new DataCache();
     private DataCache dataCache = new DataCache();
 
 
+    private String cachshedConfigurationHash;
+
+
     // --------------------------- CONSTRUCTORS ---------------------------
     // --------------------------- CONSTRUCTORS ---------------------------
 
 
     public Configuration(final StoredConfigurationImpl storedConfiguration) {
     public Configuration(final StoredConfigurationImpl storedConfiguration) {
@@ -187,6 +191,11 @@ public class Configuration implements Serializable, SettingReader {
         return JavaTypeConverter.valueToPassword(readStoredValue(setting));
         return JavaTypeConverter.valueToPassword(readStoredValue(setting));
     }
     }
 
 
+    public Map<String,NamedSecretData> readSettingAsNamedPasswords(final PwmSetting setting)
+    {
+        return JavaTypeConverter.valueToNamedPassword(readStoredValue(setting));
+    }
+
     public abstract static class JavaTypeConverter {
     public abstract static class JavaTypeConverter {
         public static long valueToLong(final StoredValue value) {
         public static long valueToLong(final StoredValue value) {
             if (!(value instanceof NumericValue)) {
             if (!(value instanceof NumericValue)) {
@@ -222,7 +231,21 @@ public class Configuration implements Serializable, SettingReader {
             }
             }
             return (PasswordData)nativeObject;
             return (PasswordData)nativeObject;
         }
         }
-        
+
+        public static Map<String,NamedSecretData> valueToNamedPassword(final StoredValue value) {
+            if (value == null) {
+                return null;
+            }
+            if ((!(value instanceof NamedSecretValue))) {
+                throw new IllegalArgumentException("setting value is not readable as named password");
+            }
+            final Object nativeObject = value.toNativeObject();
+            if (nativeObject == null) {
+                return null;
+            }
+            return (Map<String,NamedSecretData>)nativeObject;
+        }
+
         public static List<ActionConfiguration> valueToAction(final PwmSetting setting, final StoredValue storedValue) {
         public static List<ActionConfiguration> valueToAction(final PwmSetting setting, final StoredValue storedValue) {
             if (PwmSettingSyntax.ACTION != setting.getSyntax()) {
             if (PwmSettingSyntax.ACTION != setting.getSyntax()) {
                 throw new IllegalArgumentException("may not read ACTION value for setting: " + setting.toString());
                 throw new IllegalArgumentException("may not read ACTION value for setting: " + setting.toString());
@@ -882,11 +905,14 @@ public class Configuration implements Serializable, SettingReader {
     public boolean isDevDebugMode() {
     public boolean isDevDebugMode() {
         return Boolean.parseBoolean(readAppProperty(AppProperty.LOGGING_DEV_OUTPUT));
         return Boolean.parseBoolean(readAppProperty(AppProperty.LOGGING_DEV_OUTPUT));
     }
     }
-    
-    public String configurationHash() 
+
+    public String configurationHash()
             throws PwmUnrecoverableException 
             throws PwmUnrecoverableException 
     {
     {
-        return storedConfiguration.settingChecksum();
+        if (this.cachshedConfigurationHash == null) {
+            this.cachshedConfigurationHash = storedConfiguration.settingChecksum();
+        }
+        return cachshedConfigurationHash;
     }
     }
 
 
     public Set<PwmSetting> nonDefaultSettings() {
     public Set<PwmSetting> nonDefaultSettings() {

+ 6 - 0
src/main/java/password/pwm/config/FormConfiguration.java

@@ -266,6 +266,12 @@ public class FormConfiguration implements Serializable {
 
 
     public void checkValue(final Configuration config, final String value, final Locale locale)
     public void checkValue(final Configuration config, final String value, final Locale locale)
             throws PwmDataValidationException, PwmUnrecoverableException {
             throws PwmDataValidationException, PwmUnrecoverableException {
+
+        // ignore read only fields
+        if (readonly) {
+            return;
+        }
+
         //check if value is missing and required.
         //check if value is missing and required.
         if (required && (value == null || value.length() < 1)) {
         if (required && (value == null || value.length() < 1)) {
             final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, null, new String[]{getLabel(locale)});
             final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, null, new String[]{getLabel(locale)});

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

@@ -90,7 +90,7 @@ public class FormUtility {
             final String keyName = formItem.getName();
             final String keyName = formItem.getName();
             final String value = inputMap.get(keyName);
             final String value = inputMap.get(keyName);
 
 
-            if (formItem.isRequired()) {
+            if (formItem.isRequired() && !formItem.isReadonly()) {
                 if (value == null || value.length() < 0) {
                 if (value == null || value.length() < 0) {
                     final String errorMsg = "missing required value for field '" + formItem.getName() + "'";
                     final String errorMsg = "missing required value for field '" + formItem.getName() + "'";
                     final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, errorMsg, new String[]{formItem.getLabel(locale)});
                     final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, errorMsg, new String[]{formItem.getLabel(locale)});

+ 37 - 0
src/main/java/password/pwm/config/NamedSecretData.java

@@ -0,0 +1,37 @@
+/*
+ * 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.AllArgsConstructor;
+import lombok.Getter;
+import password.pwm.util.PasswordData;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Getter
+@AllArgsConstructor
+public class NamedSecretData implements Serializable {
+    private PasswordData password;
+    private List<String> usage;
+}

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

@@ -1100,7 +1100,7 @@ public enum PwmSetting {
     WEBSERVICES_THIRDPARTY_QUERY_MATCH(
     WEBSERVICES_THIRDPARTY_QUERY_MATCH(
             "webservices.thirdParty.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.REST_SERVER),
             "webservices.thirdParty.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.REST_SERVER),
     WEBSERVICES_EXTERNAL_SECRET(
     WEBSERVICES_EXTERNAL_SECRET(
-            "webservices.external.secret", PwmSettingSyntax.PASSWORD, PwmSettingCategory.REST_SERVER),
+            "webservices.external.secrets", PwmSettingSyntax.NAMED_SECRET, PwmSettingCategory.REST_SERVER),
 
 
 
 
     EXTERNAL_MACROS_DEST_TOKEN_URLS(
     EXTERNAL_MACROS_DEST_TOKEN_URLS(
@@ -1133,9 +1133,6 @@ public enum PwmSetting {
             "recovery.require.otp", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
             "recovery.require.otp", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
     HELPDESK_ENABLE_OTP_VERIFY(
     HELPDESK_ENABLE_OTP_VERIFY(
             "helpdesk.otp.verify", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_BASE),
             "helpdesk.otp.verify", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_BASE),
-    PWMDB_LOCATION(
-            "pwmDb.location", PwmSettingSyntax.STRING, PwmSettingCategory.GENERAL),
-
 
 
 
 
     ;
     ;

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

@@ -30,6 +30,7 @@ import password.pwm.config.value.FileValue;
 import password.pwm.config.value.FormValue;
 import password.pwm.config.value.FormValue;
 import password.pwm.config.value.LocalizedStringArrayValue;
 import password.pwm.config.value.LocalizedStringArrayValue;
 import password.pwm.config.value.LocalizedStringValue;
 import password.pwm.config.value.LocalizedStringValue;
+import password.pwm.config.value.NamedSecretValue;
 import password.pwm.config.value.NumericValue;
 import password.pwm.config.value.NumericValue;
 import password.pwm.config.value.OptionListValue;
 import password.pwm.config.value.OptionListValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PasswordValue;
@@ -68,6 +69,7 @@ public enum PwmSettingSyntax {
     PROFILE(StringArrayValue.factory()),
     PROFILE(StringArrayValue.factory()),
     VERIFICATION_METHOD(VerificationMethodValue.factory()),
     VERIFICATION_METHOD(VerificationMethodValue.factory()),
     PRIVATE_KEY(PrivateKeyValue.factory()),
     PRIVATE_KEY(PrivateKeyValue.factory()),
+    NAMED_SECRET(NamedSecretValue.factory()),
 
 
     ;
     ;
 
 

+ 27 - 0
src/main/java/password/pwm/config/option/WebServiceUsage.java

@@ -0,0 +1,27 @@
+/*
+ * 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.option;
+
+public enum WebServiceUsage {
+    SigningForm
+}

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

@@ -39,7 +39,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.PasswordUtility;
 
 
-import java.util.Date;
+import java.time.Instant;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
@@ -48,7 +48,7 @@ public class NewUserProfile extends AbstractProfile {
 
 
     private static final ProfileType PROFILE_TYPE = ProfileType.NewUser;
     private static final ProfileType PROFILE_TYPE = ProfileType.NewUser;
 
 
-    private Date newUserPasswordPolicyCacheTime;
+    private Instant newUserPasswordPolicyCacheTime;
     private final Map<Locale,PwmPasswordPolicy> newUserPasswordPolicyCache = new HashMap<>();
     private final Map<Locale,PwmPasswordPolicy> newUserPasswordPolicyCache = new HashMap<>();
 
 
     protected NewUserProfile(final String identifier, final Map<PwmSetting, StoredValue> storedValueMap) {
     protected NewUserProfile(final String identifier, final Map<PwmSetting, StoredValue> storedValueMap) {
@@ -78,7 +78,7 @@ public class NewUserProfile extends AbstractProfile {
         final Configuration config = pwmApplication.getConfig();
         final Configuration config = pwmApplication.getConfig();
         final long maxNewUserCacheMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CONFIG_NEWUSER_PASSWORD_POLICY_CACHE_MS));
         final long maxNewUserCacheMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CONFIG_NEWUSER_PASSWORD_POLICY_CACHE_MS));
         if (newUserPasswordPolicyCacheTime != null && TimeDuration.fromCurrent(newUserPasswordPolicyCacheTime).isLongerThan(maxNewUserCacheMS)) {
         if (newUserPasswordPolicyCacheTime != null && TimeDuration.fromCurrent(newUserPasswordPolicyCacheTime).isLongerThan(maxNewUserCacheMS)) {
-            newUserPasswordPolicyCacheTime = new Date();
+            newUserPasswordPolicyCacheTime = Instant.now();
             newUserPasswordPolicyCache.clear();
             newUserPasswordPolicyCache.clear();
         }
         }
 
 

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

@@ -41,6 +41,7 @@ import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.option.ADPolicyComplexity;
+import password.pwm.config.value.NamedSecretValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PrivateKeyValue;
 import password.pwm.config.value.PrivateKeyValue;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringArrayValue;
@@ -810,6 +811,9 @@ public class StoredConfigurationImpl implements Serializable, StoredConfiguratio
             } else if (setting.getSyntax() == PwmSettingSyntax.PRIVATE_KEY) {
             } else if (setting.getSyntax() == PwmSettingSyntax.PRIVATE_KEY) {
                 final List<Element> valueElements = ((PrivateKeyValue)value).toXmlValues("value", getKey());
                 final List<Element> valueElements = ((PrivateKeyValue)value).toXmlValues("value", getKey());
                 settingElement.addContent(valueElements);
                 settingElement.addContent(valueElements);
+            } else if (setting.getSyntax() == PwmSettingSyntax.NAMED_SECRET) {
+                final List<Element> valueElements = ((NamedSecretValue)value).toXmlValues("value", getKey());
+                settingElement.addContent(valueElements);
             } else {
             } else {
                 settingElement.addContent(value.toXmlValues("value"));
                 settingElement.addContent(value.toXmlValues("value"));
             }
             }

+ 197 - 0
src/main/java/password/pwm/config/value/NamedSecretValue.java

@@ -0,0 +1,197 @@
+/*
+ * 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.PwmConstants;
+import password.pwm.config.NamedSecretData;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.StoredValue;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+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.secure.PwmBlockAlgorithm;
+import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.SecureEngine;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class NamedSecretValue implements StoredValue {
+    private static final String ELEMENT_NAME = "name";
+    private static final String ELEMENT_PASSWORD = "password";
+    private static final String ELEMENT_USAGE = "usage";
+
+    private Map<String,NamedSecretData> values;
+
+    NamedSecretValue() {
+    }
+
+
+    public NamedSecretValue(final Map<String,NamedSecretData> values) {
+        this.values = values;
+    }
+
+    public static StoredValue.StoredValueFactory factory()
+    {
+        return new StoredValue.StoredValueFactory() {
+            public NamedSecretValue fromJson(final String value)
+            {
+                try {
+                    final Map<String,NamedSecretData> values = JsonUtil.deserialize(value,new TypeToken<Map<String,NamedSecretData>>() {
+                    }.getType());
+                    final Map<String,NamedSecretData> linkedValues = new LinkedHashMap<>(values);
+                    return new NamedSecretValue(linkedValues);
+                } catch (Exception e) {
+                    throw new IllegalStateException(
+                            "NamedPasswordValue can not be json de-serialized: " + e.getMessage());
+                }
+            }
+
+            public NamedSecretValue fromXmlElement(
+                    final Element settingElement,
+                    final PwmSecurityKey key
+            )
+                    throws PwmOperationalException, PwmUnrecoverableException
+            {
+                final Map<String,NamedSecretData> values = new LinkedHashMap<>();
+                final List<Element> valueElements = settingElement.getChildren("value");
+
+                try {
+                    if (valueElements != null) {
+                        for (final Element value : valueElements) {
+                            if (value.getChild(ELEMENT_NAME) != null && value.getChild(ELEMENT_PASSWORD) != null) {
+                                final String name = value.getChild(ELEMENT_NAME).getText();
+                                final String encodedValue = value.getChild(ELEMENT_PASSWORD).getText();
+                                final PasswordData passwordData = new PasswordData(SecureEngine.decryptStringValue(encodedValue, key, PwmBlockAlgorithm.CONFIG));
+                                final List<Element> usages = value.getChildren(ELEMENT_USAGE);
+                                final List<String> strUsages = new ArrayList<>();
+                                if (usages != null) {
+                                    for (final Element usageElement : usages) {
+                                        strUsages.add(usageElement.getText());
+                                    }
+                                }
+                                values.put(name, new NamedSecretData(passwordData, Collections.unmodifiableList(strUsages)));
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    final String errorMsg = "unable to decode encrypted password value for setting: " + e.getMessage();
+                    final ErrorInformation errorInfo = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR, errorMsg);
+                    throw new PwmOperationalException(errorInfo);
+                }
+                return new NamedSecretValue(values);
+            }
+        };
+    }
+
+    public List<Element> toXmlValues(final String valueElementName) {
+        throw new IllegalStateException("password xml output requires hash key");
+    }
+
+    @Override
+    public Object toNativeObject()
+    {
+        return values;
+    }
+
+    @Override
+    public List<String> validateValue(final PwmSetting pwm)
+    {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public int currentSyntaxVersion()
+    {
+        return 0;
+    }
+
+    public List<Element> toXmlValues(final String valueElementName, final PwmSecurityKey key) {
+        if (values == null) {
+            final Element valueElement = new Element(valueElementName);
+            return Collections.singletonList(valueElement);
+        }
+        final List<Element> valuesElement = new ArrayList<>();
+        try {
+            for (final String name : values.keySet()) {
+                final PasswordData passwordData = values.get(name).getPassword();
+                final String encodedValue = SecureEngine.encryptToString(passwordData.getStringValue(), key, PwmBlockAlgorithm.CONFIG);
+                final Element newValueElement = new Element("value");
+                final Element nameElement = new Element(ELEMENT_NAME);
+                nameElement.setText(name);
+                final Element encodedValueElement = new Element(ELEMENT_PASSWORD);
+                encodedValueElement.setText(encodedValue);
+
+                newValueElement.addContent(nameElement);
+                newValueElement.addContent(encodedValueElement);
+
+                for (final String usages : values.get(name).getUsage()) {
+                    final Element usageElement = new Element(ELEMENT_USAGE);
+                    usageElement.setText(usages);
+                    newValueElement.addContent(usageElement);
+                }
+
+
+                valuesElement.add(newValueElement);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("missing required AES and SHA1 libraries, or other crypto fault: " + e.getMessage());
+        }
+        return Collections.unmodifiableList(valuesElement);
+    }
+
+    public String toString() {
+        return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT;
+    }
+
+    @Override
+    public String toDebugString(final Locale locale) {
+        return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT;
+    }
+
+    @Override
+    public Serializable toDebugJsonObject(final Locale locale) {
+        return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT;
+    }
+
+    public boolean requiresStoredUpdate()
+    {
+        return false;
+    }
+
+    @Override
+    public String valueHash() throws PwmUnrecoverableException {
+        return values == null ? "" : SecureEngine.hash(JsonUtil.serializeMap(values), PwmConstants.SETTING_CHECKSUM_HASH_METHOD);
+    }
+
+}

+ 86 - 19
src/main/java/password/pwm/http/IdleTimeoutCalculator.java

@@ -22,65 +22,132 @@
 
 
 package password.pwm.http;
 package password.pwm.http;
 
 
+import lombok.AllArgsConstructor;
+import lombok.Getter;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
+import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.HelpdeskProfile;
+import password.pwm.config.profile.ProfileType;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.PwmServletDefinition;
+import password.pwm.ldap.UserInfo;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
+import java.util.Collections;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeUnit;
 
 
 public class IdleTimeoutCalculator {
 public class IdleTimeoutCalculator {
     private static final PwmLogger LOGGER = PwmLogger.forClass(IdleTimeoutCalculator.class);
     private static final PwmLogger LOGGER = PwmLogger.forClass(IdleTimeoutCalculator.class);
 
 
-    public static TimeDuration figureMaxIdleTimeout(final PwmApplication pwmApplication, final PwmSession pwmSession) throws PwmUnrecoverableException
+    public static MaxIdleTimeoutResult figureMaxSessionTimeout(final PwmApplication pwmApplication, final PwmSession pwmSession)
+            throws PwmUnrecoverableException
     {
     {
         final Configuration configuration = pwmApplication.getConfig();
         final Configuration configuration = pwmApplication.getConfig();
-        long idleSeconds = configuration.readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
+        final SortedSet<MaxIdleTimeoutResult> results = new TreeSet<>();
+        {
+            final long idleSetting = configuration.readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
+            results.add(new MaxIdleTimeoutResult(
+                    MaxIdleTimeoutResult.reasonFor(PwmSetting.IDLE_TIMEOUT_SECONDS, null),
+                    new TimeDuration(idleSetting, TimeUnit.SECONDS)));
+        }
 
 
         if (!pwmSession.isAuthenticated()) {
         if (!pwmSession.isAuthenticated()) {
+            if (pwmApplication.getApplicationMode() == PwmApplicationMode.NEW) {
+                final long configGuideIdleTimeout = Long.parseLong(configuration.readAppProperty(AppProperty.CONFIG_GUIDE_IDLE_TIMEOUT));
+                results.add(new MaxIdleTimeoutResult(
+                        "Configuration Guide Idle Timeout",
+                        new TimeDuration(configGuideIdleTimeout, TimeUnit.SECONDS)));
+            }
+
             if (configuration.readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC)) {
             if (configuration.readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC)) {
                 final long peopleSearchIdleTimeout = configuration.readSettingAsLong(PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS);
                 final long peopleSearchIdleTimeout = configuration.readSettingAsLong(PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS);
-                idleSeconds = Math.max(idleSeconds, peopleSearchIdleTimeout);
+                if (peopleSearchIdleTimeout > 0) {
+                    results.add(new MaxIdleTimeoutResult(
+                            MaxIdleTimeoutResult.reasonFor(PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, null),
+                            new TimeDuration(peopleSearchIdleTimeout, TimeUnit.SECONDS)));
+                }
             }
             }
 
 
-            return TimeDuration.fromCurrent(idleSeconds);
+        } else {
+            final UserInfo userInfo= pwmSession.getUserInfo();
+            final boolean userIsAdmin = pwmSession.getSessionManager().checkPermission(pwmApplication, Permission.PWMADMIN);
+            final Set<MaxIdleTimeoutResult> loggedInResults = figureMaxAuthUserTimeout(configuration, userInfo, userIsAdmin);
+            results.addAll(loggedInResults);
+        }
+
+        return results.last();
+    }
+
+    private static Set<MaxIdleTimeoutResult> figureMaxAuthUserTimeout(
+            final Configuration configuration,
+            final UserInfo userInfo,
+            final boolean userIsAdmin
+    )
+            throws PwmUnrecoverableException
+    {
+        final Set<MaxIdleTimeoutResult> results = new TreeSet<>();
+        {
+            final long idleSetting = configuration.readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
+            results.add(new MaxIdleTimeoutResult(
+                    MaxIdleTimeoutResult.reasonFor(PwmSetting.IDLE_TIMEOUT_SECONDS, null),
+                    new TimeDuration(idleSetting, TimeUnit.SECONDS)));
         }
         }
 
 
         if (configuration.readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE)) {
         if (configuration.readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE)) {
-            final HelpdeskProfile helpdeskProfile = pwmSession.getSessionManager().getHelpdeskProfile(pwmApplication);
-            if (helpdeskProfile != null) {
+            final String helpdeskProfileID = userInfo.getProfileIDs().get(ProfileType.Helpdesk);
+            if (!StringUtil.isEmpty(helpdeskProfileID)) {
+                final HelpdeskProfile helpdeskProfile = configuration.getHelpdeskProfiles().get(helpdeskProfileID);
                 final long helpdeskIdleTimeout = helpdeskProfile.readSettingAsLong(PwmSetting.HELPDESK_IDLE_TIMEOUT_SECONDS);
                 final long helpdeskIdleTimeout = helpdeskProfile.readSettingAsLong(PwmSetting.HELPDESK_IDLE_TIMEOUT_SECONDS);
-                idleSeconds = Math.max(idleSeconds, helpdeskIdleTimeout);
+                results.add(new MaxIdleTimeoutResult(
+                        MaxIdleTimeoutResult.reasonFor(PwmSetting.HELPDESK_IDLE_TIMEOUT_SECONDS, helpdeskProfileID),
+                        new TimeDuration(helpdeskIdleTimeout, TimeUnit.SECONDS)));
             }
             }
         }
         }
 
 
         if (configuration.readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE)) {
         if (configuration.readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE)) {
             final long peopleSearchIdleTimeout = configuration.readSettingAsLong(PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS);
             final long peopleSearchIdleTimeout = configuration.readSettingAsLong(PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS);
-            idleSeconds = Math.max(idleSeconds, peopleSearchIdleTimeout);
+            if (peopleSearchIdleTimeout > 0) {
+                results.add(new MaxIdleTimeoutResult(
+                        MaxIdleTimeoutResult.reasonFor(PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, null),
+                        new TimeDuration(peopleSearchIdleTimeout, TimeUnit.SECONDS)));
+            }
         }
         }
 
 
-        if (pwmApplication.getApplicationMode() == PwmApplicationMode.NEW) {
-            final long configGuideIdleTimeout = Long.parseLong(configuration.readAppProperty(AppProperty.CONFIG_GUIDE_IDLE_TIMEOUT));
-            idleSeconds = Math.max(idleSeconds, configGuideIdleTimeout);
+        if (userIsAdmin) {
+            final long configEditorIdleTimeout = Long.parseLong(configuration.readAppProperty(AppProperty.CONFIG_EDITOR_IDLE_TIMEOUT));
+            results.add(new MaxIdleTimeoutResult(
+                    "Config Editor Idle Timeout",
+                    new TimeDuration(configEditorIdleTimeout, TimeUnit.SECONDS)));
         }
         }
 
 
-        try {
-            if (pwmSession.getSessionManager().checkPermission(pwmApplication, Permission.PWMADMIN)) {
-                final long configEditorIdleTimeout = Long.parseLong(configuration.readAppProperty(AppProperty.CONFIG_EDITOR_IDLE_TIMEOUT));
-                idleSeconds = Math.max(idleSeconds, configEditorIdleTimeout);
-            }
-        } catch (PwmUnrecoverableException e) {
-            LOGGER.error(pwmSession,"error while figuring max idle timeout for session: " + e.getMessage());
+        return Collections.unmodifiableSet(results);
+    }
+
+    @Getter
+    @AllArgsConstructor
+    static class MaxIdleTimeoutResult implements Comparable<MaxIdleTimeoutResult> {
+        private final String reason;
+        private final TimeDuration idleTimeout;
+
+        @Override
+        public int compareTo(final MaxIdleTimeoutResult o)
+        {
+            return this.idleTimeout.compareTo(o.getIdleTimeout());
         }
         }
 
 
-        return new TimeDuration(idleSeconds, TimeUnit.SECONDS);
+        static String reasonFor(final PwmSetting pwmSetting, final String profileID) {
+            return "Setting " + pwmSetting.toMenuLocationDebug(profileID, PwmConstants.DEFAULT_LOCALE);
+        }
     }
     }
 
 
     public static TimeDuration idleTimeoutForRequest(final PwmRequest pwmRequest) throws PwmUnrecoverableException
     public static TimeDuration idleTimeoutForRequest(final PwmRequest pwmRequest) throws PwmUnrecoverableException

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

@@ -38,6 +38,7 @@ public enum PwmRequestAttribute {
     CspNonce,
     CspNonce,
 
 
     FormConfiguration,
     FormConfiguration,
+    FormInitialValues,
     FormReadOnly,
     FormReadOnly,
     FormShowPasswordFields,
     FormShowPasswordFields,
     FormData,
     FormData,

+ 17 - 3
src/main/java/password/pwm/http/PwmSessionWrapper.java

@@ -27,12 +27,13 @@ import password.pwm.PwmConstants;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
 
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSession;
 
 
 public class PwmSessionWrapper {
 public class PwmSessionWrapper {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(PwmSessionWrapper.class);
 
 
     private transient PwmSession pwmSession;
     private transient PwmSession pwmSession;
 
 
@@ -49,8 +50,7 @@ public class PwmSessionWrapper {
     {
     {
         httpSession.setAttribute(PwmConstants.SESSION_ATTR_PWM_SESSION, pwmSession);
         httpSession.setAttribute(PwmConstants.SESSION_ATTR_PWM_SESSION, pwmSession);
 
 
-        final TimeDuration maxIdleTime = IdleTimeoutCalculator.figureMaxIdleTimeout(pwmApplication, pwmSession);
-        httpSession.setMaxInactiveInterval((int) maxIdleTime.getTotalSeconds());
+        setHttpSessionIdleTimeout(pwmApplication, pwmSession, httpSession);
     }
     }
 
 
 
 
@@ -67,4 +67,18 @@ public class PwmSessionWrapper {
     public static PwmSession readPwmSession(final HttpServletRequest httpRequest) throws PwmUnrecoverableException {
     public static PwmSession readPwmSession(final HttpServletRequest httpRequest) throws PwmUnrecoverableException {
         return readPwmSession(httpRequest.getSession());
         return readPwmSession(httpRequest.getSession());
     }
     }
+
+    public static void setHttpSessionIdleTimeout(
+            final PwmApplication pwmApplication,
+            final PwmSession pwmSession,
+            final HttpSession httpSession
+    ) throws PwmUnrecoverableException
+    {
+        final IdleTimeoutCalculator.MaxIdleTimeoutResult result = IdleTimeoutCalculator.figureMaxSessionTimeout(pwmApplication, pwmSession);
+        if (httpSession.getMaxInactiveInterval() != result.getIdleTimeout().getTotalSeconds()) {
+            httpSession.setMaxInactiveInterval((int) result.getIdleTimeout().getTotalSeconds());
+            LOGGER.trace(pwmSession, "setting java servlet session timeout to " + result.getIdleTimeout().asCompactString()
+                    + " due to " + result.getReason());
+        }
+    }
 }
 }

+ 17 - 37
src/main/java/password/pwm/http/bean/NewUserBean.java

@@ -22,16 +22,14 @@
 
 
 package password.pwm.http.bean;
 package password.pwm.http.bean;
 
 
-import lombok.AllArgsConstructor;
+import com.google.gson.annotations.SerializedName;
 import lombok.Getter;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.Setter;
 import password.pwm.bean.TokenVerificationProgress;
 import password.pwm.bean.TokenVerificationProgress;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.option.SessionBeanMode;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.PasswordData;
+import password.pwm.http.servlet.newuser.NewUserForm;
 
 
-import java.io.Serializable;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Collections;
@@ -43,49 +41,31 @@ import java.util.Set;
 @Setter
 @Setter
 @NoArgsConstructor
 @NoArgsConstructor
 public class NewUserBean extends PwmSessionBean {
 public class NewUserBean extends PwmSessionBean {
+    @SerializedName("p")
     private String profileID;
     private String profileID;
+
+    @SerializedName("f")
     private NewUserForm newUserForm;
     private NewUserForm newUserForm;
 
 
+    @SerializedName("r")
     private Map<String,String> remoteInputData;
     private Map<String,String> remoteInputData;
-    private boolean agreementPassed;
-    private boolean formPassed;
-    private Instant createStartTime;
-    private boolean urlSpecifiedProfile;
-    private final TokenVerificationProgress tokenVerificationProgress = new TokenVerificationProgress();
-
-    @Getter
-    @AllArgsConstructor
-    public static class
-    NewUserForm implements Serializable {
-        private final Map<String,String> formData;
-        private final PasswordData newUserPassword;
-        private final PasswordData confirmPassword;
 
 
-        public boolean isConsistentWith(final NewUserForm otherForm) throws PwmUnrecoverableException {
-            if (otherForm == null) {
-                return false;
-            }
+    @SerializedName("ap")
+    private boolean agreementPassed;
 
 
-            if (newUserPassword != null && otherForm.newUserPassword == null || newUserPassword == null && otherForm.newUserPassword != null) {
-                return false;
-            }
+    @SerializedName("fp")
+    private boolean formPassed;
 
 
-            if (newUserPassword == null || !newUserPassword.getStringValue().equals(otherForm.newUserPassword.getStringValue())) {
-                return false;
-            }
+    @SerializedName("t")
+    private Instant createStartTime;
 
 
-            for (final String formKey : formData.keySet()) {
-                final String value = formData.get(formKey);
-                final String otherValue = otherForm.formData.get(formKey);
-                if (value != null && !value.equals(otherValue)) {
-                    return false;
-                }
-            }
+    @SerializedName("u")
+    private boolean urlSpecifiedProfile;
 
 
-            return true;
-        }
-    }
+    @SerializedName("v")
+    private final TokenVerificationProgress tokenVerificationProgress = new TokenVerificationProgress();
 
 
+    @Override
     public Type getType() {
     public Type getType() {
         return Type.PUBLIC;
         return Type.PUBLIC;
     }
     }

+ 6 - 26
src/main/java/password/pwm/http/bean/PwmSessionBean.java

@@ -22,13 +22,17 @@
 
 
 package password.pwm.http.bean;
 package password.pwm.http.bean;
 
 
+import lombok.Getter;
+import lombok.Setter;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
-import java.util.Date;
+import java.time.Instant;
 import java.util.Set;
 import java.util.Set;
 
 
+@Getter
+@Setter
 public abstract class PwmSessionBean implements Serializable {
 public abstract class PwmSessionBean implements Serializable {
     public enum Type {
     public enum Type {
         PUBLIC,
         PUBLIC,
@@ -36,33 +40,9 @@ public abstract class PwmSessionBean implements Serializable {
     }
     }
 
 
     private String guid;
     private String guid;
-    private Date timestamp;
+    private Instant timestamp;
     private ErrorInformation lastError;
     private ErrorInformation lastError;
 
 
-    public String getGuid() {
-        return guid;
-    }
-
-    public void setGuid(final String guid) {
-        this.guid = guid;
-    }
-
-    public Date getTimestamp() {
-        return timestamp;
-    }
-
-    public void setTimestamp(final Date timestamp) {
-        this.timestamp = timestamp;
-    }
-
-    public ErrorInformation getLastError() {
-        return lastError;
-    }
-
-    public void setLastError(final ErrorInformation lastError) {
-        this.lastError = lastError;
-    }
-
     public abstract Type getType();
     public abstract Type getType();
 
 
     public abstract Set<SessionBeanMode> supportedModes();
     public abstract Set<SessionBeanMode> supportedModes();

+ 4 - 7
src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -468,11 +468,8 @@ public class RequestInitializationFilter implements Filter {
             initializeLocaleAndTheme(pwmRequest);
             initializeLocaleAndTheme(pwmRequest);
         }
         }
 
 
-        // set idle timeout (may get overridden by module-specific values elsewhere
-        if (!pwmURL.isResourceURL() && !pwmURL.isCommandServletURL() && !pwmURL.isWebServiceURL()){
-            final TimeDuration maxIdleTimeout = IdleTimeoutCalculator.figureMaxIdleTimeout(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession());
-            pwmRequest.getHttpServletRequest().getSession().setMaxInactiveInterval((int) maxIdleTimeout.getTotalSeconds());
-        }
+        // set idle timeout
+        PwmSessionWrapper.setHttpSessionIdleTimeout(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest().getSession());
     }
     }
 
 
     private static void initializeLocaleAndTheme(
     private static void initializeLocaleAndTheme(
@@ -665,9 +662,9 @@ public class RequestInitializationFilter implements Filter {
         final TimeDuration maxDurationForRequest = IdleTimeoutCalculator.idleTimeoutForRequest(pwmRequest);
         final TimeDuration maxDurationForRequest = IdleTimeoutCalculator.idleTimeoutForRequest(pwmRequest);
         final TimeDuration currentDuration = TimeDuration.fromCurrent(pwmRequest.getHttpServletRequest().getSession().getLastAccessedTime());
         final TimeDuration currentDuration = TimeDuration.fromCurrent(pwmRequest.getHttpServletRequest().getSession().getLastAccessedTime());
         if (currentDuration.isLongerThan(maxDurationForRequest)) {
         if (currentDuration.isLongerThan(maxDurationForRequest)) {
-            LOGGER.debug("closing session due to idle time, max for request is " + maxDurationForRequest.asCompactString() + ", session idle time is " + currentDuration.asCompactString());
+            LOGGER.debug("unauthenticated session due to idle time, max for request is " + maxDurationForRequest.asCompactString()
+                    + ", session idle time is " + currentDuration.asCompactString());
             pwmRequest.getPwmSession().unauthenticateUser(pwmRequest);
             pwmRequest.getPwmSession().unauthenticateUser(pwmRequest);
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_USERAUTHENTICATED,"idle timeout exceeded"));
         }
         }
     }
     }
 
 

+ 71 - 0
src/main/java/password/pwm/http/servlet/newuser/NewUserForm.java

@@ -0,0 +1,71 @@
+/*
+ * 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.newuser;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.PasswordData;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Getter
+@AllArgsConstructor
+public class NewUserForm implements Serializable {
+
+    @SerializedName("f")
+    private final Map<String,String> formData;
+
+    @SerializedName("p")
+    private final PasswordData newUserPassword;
+
+    @SerializedName("c")
+    private final PasswordData confirmPassword;
+
+    public boolean isConsistentWith(final NewUserForm otherForm) throws PwmUnrecoverableException
+    {
+        if (otherForm == null) {
+            return false;
+        }
+
+        if (newUserPassword != null && otherForm.newUserPassword == null || newUserPassword == null && otherForm.newUserPassword != null) {
+            return false;
+        }
+
+        if (newUserPassword == null || !newUserPassword.getStringValue().equals(otherForm.newUserPassword.getStringValue())) {
+            return false;
+        }
+
+        for (final String formKey : formData.keySet()) {
+            final String value = formData.get(formKey);
+            final String otherValue = otherForm.formData.get(formKey);
+            if (value != null && !value.equals(otherValue)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}

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

@@ -22,12 +22,11 @@
 
 
 package password.pwm.http.servlet.newuser;
 package password.pwm.http.servlet.newuser;
 
 
-import password.pwm.AppProperty;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.FormUtility;
 import password.pwm.config.FormUtility;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.config.profile.NewUserProfile;
-import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
@@ -36,11 +35,12 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.NewUserBean;
 import password.pwm.http.bean.NewUserBean;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
-import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.SecureService;
 
 
 import java.io.IOException;
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
@@ -51,48 +51,31 @@ class NewUserFormUtils {
     private static final PwmLogger LOGGER = PwmLogger.forClass(NewUserFormUtils.class);
     private static final PwmLogger LOGGER = PwmLogger.forClass(NewUserFormUtils.class);
 
 
 
 
-    static NewUserBean.NewUserForm readFromRequest(
+    static NewUserForm readFromRequest(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
             final NewUserBean newUserBean
             final NewUserBean newUserBean
 
 
     )
     )
             throws PwmDataValidationException, PwmUnrecoverableException
             throws PwmDataValidationException, PwmUnrecoverableException
     {
     {
-        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
-        final boolean promptForPassword = newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD);
 
 
         final Locale userLocale = pwmRequest.getLocale();
         final Locale userLocale = pwmRequest.getLocale();
         final List<FormConfiguration> newUserForm = NewUserServlet.getFormDefinition(pwmRequest);
         final List<FormConfiguration> newUserForm = NewUserServlet.getFormDefinition(pwmRequest);
         final Map<FormConfiguration, String> userFormValues = FormUtility.readFormValuesFromRequest(pwmRequest,
         final Map<FormConfiguration, String> userFormValues = FormUtility.readFormValuesFromRequest(pwmRequest,
                 newUserForm, userLocale);
                 newUserForm, userLocale);
-        final PasswordData passwordData1;
-        final PasswordData passwordData2;
-        if (promptForPassword) {
-            passwordData1 = pwmRequest.readParameterAsPassword(NewUserServlet.FIELD_PASSWORD1);
-            passwordData2 = pwmRequest.readParameterAsPassword(NewUserServlet.FIELD_PASSWORD2);
-        } else {
-            final PwmPasswordPolicy pwmPasswordPolicy = newUserProfile.getNewUserPasswordPolicy(pwmRequest.getPwmApplication(), pwmRequest.getLocale());
-            final PasswordData password = RandomPasswordGenerator.createRandomPassword(pwmRequest.getSessionLabel(), pwmPasswordPolicy, pwmRequest.getPwmApplication());
-            passwordData1 = password;
-            passwordData2 = password;
-        }
-
-        final Map<String,String> mergedData = new LinkedHashMap<>(FormUtility.asStringMap(userFormValues));
-        if (newUserBean.getRemoteInputData() != null) {
-            mergedData.putAll(newUserBean.getRemoteInputData());
-        }
+        final PasswordData passwordData1 = pwmRequest.readParameterAsPassword(NewUserServlet.FIELD_PASSWORD1);
+        final PasswordData passwordData2 = pwmRequest.readParameterAsPassword(NewUserServlet.FIELD_PASSWORD2);
 
 
-        return new NewUserBean.NewUserForm(mergedData, passwordData1, passwordData2);
+        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
+        return injectRemoteValuesIntoForm(userFormValues, newUserBean.getRemoteInputData(), newUserProfile, passwordData1, passwordData2);
     }
     }
 
 
-    static NewUserBean.NewUserForm readFromJsonRequest(
-            final PwmRequest pwmRequest
+    static NewUserForm readFromJsonRequest(
+            final PwmRequest pwmRequest,
+            final NewUserBean newUserBean
     )
     )
             throws IOException, PwmUnrecoverableException, PwmDataValidationException
             throws IOException, PwmUnrecoverableException, PwmDataValidationException
     {
     {
-        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
-
-        final boolean promptForPassword = newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD);
 
 
         final Locale userLocale = pwmRequest.getLocale();
         final Locale userLocale = pwmRequest.getLocale();
         final List<FormConfiguration> newUserForm = NewUserServlet.getFormDefinition(pwmRequest);
         final List<FormConfiguration> newUserForm = NewUserServlet.getFormDefinition(pwmRequest);
@@ -100,24 +83,18 @@ class NewUserFormUtils {
         final Map<FormConfiguration, String> userFormValues = FormUtility.readFormValuesFromMap(jsonBodyMap,
         final Map<FormConfiguration, String> userFormValues = FormUtility.readFormValuesFromMap(jsonBodyMap,
                 newUserForm, userLocale);
                 newUserForm, userLocale);
 
 
-        final PasswordData passwordData1;
-        final PasswordData passwordData2;
-        if (promptForPassword) {
-            passwordData1 = jsonBodyMap.containsKey(NewUserServlet.FIELD_PASSWORD1) && !jsonBodyMap.get(
-                    NewUserServlet.FIELD_PASSWORD1).isEmpty()
-                    ? new PasswordData(jsonBodyMap.get(NewUserServlet.FIELD_PASSWORD1))
-                    : null;
-            passwordData2 = jsonBodyMap.containsKey(NewUserServlet.FIELD_PASSWORD2) && !jsonBodyMap.get(
-                    NewUserServlet.FIELD_PASSWORD2).isEmpty()
-                    ? new PasswordData(jsonBodyMap.get(NewUserServlet.FIELD_PASSWORD2))
-                    : null;
-        } else {
-            final PwmPasswordPolicy pwmPasswordPolicy = newUserProfile.getNewUserPasswordPolicy(pwmRequest.getPwmApplication(), pwmRequest.getLocale());
-            final PasswordData password = RandomPasswordGenerator.createRandomPassword(pwmRequest.getSessionLabel(), pwmPasswordPolicy, pwmRequest.getPwmApplication());
-            passwordData1 = password;
-            passwordData2 = password;
-        }
-        return new NewUserBean.NewUserForm(FormUtility.asStringMap(userFormValues), passwordData1, passwordData2);
+        final PasswordData passwordData1 = jsonBodyMap.containsKey(NewUserServlet.FIELD_PASSWORD1) && !jsonBodyMap.get(
+                NewUserServlet.FIELD_PASSWORD1).isEmpty()
+                ? new PasswordData(jsonBodyMap.get(NewUserServlet.FIELD_PASSWORD1))
+                : null;
+
+        final PasswordData passwordData2 = jsonBodyMap.containsKey(NewUserServlet.FIELD_PASSWORD2) && !jsonBodyMap.get(
+                NewUserServlet.FIELD_PASSWORD2).isEmpty()
+                ? new PasswordData(jsonBodyMap.get(NewUserServlet.FIELD_PASSWORD2))
+                : null;
+
+        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
+        return injectRemoteValuesIntoForm(userFormValues, newUserBean.getRemoteInputData(), newUserProfile, passwordData1, passwordData2);
     }
     }
 
 
     static NewUserTokenData fromTokenPayload(
     static NewUserTokenData fromTokenPayload(
@@ -126,70 +103,40 @@ class NewUserFormUtils {
     )
     )
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final Locale userLocale = pwmRequest.getLocale();
+        final SecureService secureService = pwmRequest.getPwmApplication().getSecureService();
 
 
         final Map<String, String> payloadMap = tokenPayload.getData();
         final Map<String, String> payloadMap = tokenPayload.getData();
 
 
-        final NewUserProfile newUserProfile;
-        {
-            final String profileID = payloadMap.get(NewUserServlet.TOKEN_PAYLOAD_ATTR);
-            payloadMap.remove(NewUserServlet.TOKEN_PAYLOAD_ATTR);
-            if (profileID == null || profileID.isEmpty()) {
-                // typically missing because issued with code before newuser profile existed, so assume  only profile
-                if (pwmRequest.getConfig().getNewUserProfiles().size() > 1) {
-                    throw new PwmOperationalException(PwmError.ERROR_TOKEN_INCORRECT, "token data missing reference to new user profileID");
-                }
-                newUserProfile = pwmRequest.getConfig().getNewUserProfiles().values().iterator().next();
-            } else {
-                if (!pwmRequest.getConfig().getNewUserProfiles().keySet().contains(profileID)) {
-                    throw new PwmOperationalException(PwmError.ERROR_TOKEN_INCORRECT, "token data references an invalid new user profileID");
-                }
-                newUserProfile = pwmRequest.getConfig().getNewUserProfiles().get(profileID);
-            }
+        if (!payloadMap.containsKey(NewUserServlet.TOKEN_PAYLOAD_ATTR)) {
+            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT, "token is missing new user form data"));
         }
         }
 
 
-        final List<FormConfiguration> newUserFormDefinition = newUserProfile.readSettingAsForm(PwmSetting.NEWUSER_FORM);
-        final Map<FormConfiguration, String> userFormValues = FormUtility.readFormValuesFromMap(payloadMap,
-                newUserFormDefinition, userLocale);
-        final PasswordData passwordData;
-        if (payloadMap.containsKey(NewUserServlet.FIELD_PASSWORD1)) {
-            final String passwordInToken = payloadMap.get(NewUserServlet.FIELD_PASSWORD1);
-            String decryptedPassword = passwordInToken;
-            try {
-                decryptedPassword = pwmRequest.getPwmApplication().getSecureService().decryptStringValue(passwordInToken);
-            } catch (PwmUnrecoverableException e) {
-                final boolean allowUnencryptedPassword = Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.NEWUSER_TOKEN_ALLOW_PLAIN_PW));
-                if (allowUnencryptedPassword && e.getError() == PwmError.ERROR_CRYPT_ERROR) {
-                    LOGGER.warn(pwmRequest, "error decrypting password in tokenPayload, will use raw password value: " + e.getMessage());
-                } else {
-                    throw e;
-                }
-            }
-            passwordData = new PasswordData(decryptedPassword);
-        } else {
-            passwordData = null;
-        }
-        final NewUserBean.NewUserForm newUserForm = new NewUserBean.NewUserForm(FormUtility.asStringMap(userFormValues), passwordData, passwordData);
-        return new NewUserTokenData(newUserProfile.getIdentifier(), newUserForm);
+        final String encryptedTokenData = payloadMap.get(NewUserServlet.TOKEN_PAYLOAD_ATTR);
+
+        return secureService.decryptObject(encryptedTokenData, NewUserTokenData.class);
     }
     }
 
 
     static Map<String, String> toTokenPayload(
     static Map<String, String> toTokenPayload(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final NewUserBean.NewUserForm newUserForm
+            final NewUserBean newUserBean
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Map<String, String> payloadMap = new LinkedHashMap<>();
-        payloadMap.put(NewUserServlet.TOKEN_PAYLOAD_ATTR, pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, NewUserBean.class).getProfileID());
-        payloadMap.putAll(newUserForm.getFormData());
-        final String encryptedPassword = pwmRequest.getPwmApplication().getSecureService().encryptToString(
-                newUserForm.getNewUserPassword().getStringValue()
+
+        final NewUserTokenData newUserTokenData = new NewUserTokenData(
+                newUserBean.getProfileID(),
+                newUserBean.getNewUserForm(),
+                newUserBean.getRemoteInputData()
         );
         );
-        payloadMap.put(NewUserServlet.FIELD_PASSWORD1, encryptedPassword);
+
+        final SecureService secureService = pwmRequest.getPwmApplication().getSecureService();
+        final String encodedTokenData = secureService.encryptObjectToString(newUserTokenData);
+        final Map<String, String> payloadMap = new HashMap<>();
+        payloadMap.put(NewUserServlet.TOKEN_PAYLOAD_ATTR, encodedTokenData);
         return payloadMap;
         return payloadMap;
     }
     }
 
 
-    static Map<String,String> getLdapDataFromNewUserForm(final NewUserProfile newUserProfile, final NewUserBean.NewUserForm newUserForm) {
+    static Map<String,String> getLdapDataFromNewUserForm(final NewUserProfile newUserProfile, final NewUserForm newUserForm) {
         final Map<String,String> ldapData = new LinkedHashMap<>();
         final Map<String,String> ldapData = new LinkedHashMap<>();
         final List<FormConfiguration> formConfigurations = newUserProfile.readSettingAsForm(PwmSetting.NEWUSER_FORM);
         final List<FormConfiguration> formConfigurations = newUserProfile.readSettingAsForm(PwmSetting.NEWUSER_FORM);
         for (final FormConfiguration formConfiguration : formConfigurations) {
         for (final FormConfiguration formConfiguration : formConfigurations) {
@@ -201,4 +148,29 @@ class NewUserFormUtils {
         }
         }
         return ldapData;
         return ldapData;
     }
     }
+
+    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<>();
+        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))
+                {
+                    newFormValues.put(formConfiguration.getName(), injectedValues.get(formConfiguration.getName()));
+                }
+            }
+        }
+
+        return new NewUserForm(newFormValues, passwordData1, passwordData2);
+    }
 }
 }

+ 37 - 18
src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -72,6 +72,7 @@ import java.time.Instant;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
@@ -97,7 +98,7 @@ public class NewUserServlet extends ControlledPwmServlet {
 
 
     static final String FIELD_PASSWORD1 = "password1";
     static final String FIELD_PASSWORD1 = "password1";
     static final String FIELD_PASSWORD2 = "password2";
     static final String FIELD_PASSWORD2 = "password2";
-    static final String TOKEN_PAYLOAD_ATTR = "_______profileID";
+    static final String TOKEN_PAYLOAD_ATTR = "p";
 
 
     public enum NewUserAction implements AbstractPwmServlet.ProcessAction {
     public enum NewUserAction implements AbstractPwmServlet.ProcessAction {
         profileChoice(HttpMethod.POST, HttpMethod.POST),
         profileChoice(HttpMethod.POST, HttpMethod.POST),
@@ -148,8 +149,8 @@ public class NewUserServlet extends ControlledPwmServlet {
 
 
         final String signedFormData = pwmRequest.readParameterAsString("signedForm", PwmHttpRequestWrapper.Flag.BypassValidation);
         final String signedFormData = pwmRequest.readParameterAsString("signedForm", PwmHttpRequestWrapper.Flag.BypassValidation);
         if (!StringUtil.isEmpty(signedFormData)) {
         if (!StringUtil.isEmpty(signedFormData)) {
-            LOGGER.trace("detected signedForm parameter in request, will read and place in bean.");
             final Map<String,String> jsonForm = RestSigningServer.readSignedFormValue(pwmApplication, signedFormData);
             final Map<String,String> jsonForm = RestSigningServer.readSignedFormValue(pwmApplication, signedFormData);
+            LOGGER.trace("detected signedForm parameter in request, will read and place in bean; keys=" + JsonUtil.serializeCollection(jsonForm.keySet()));
             newUserBean.setRemoteInputData(jsonForm);
             newUserBean.setRemoteInputData(jsonForm);
         }
         }
 
 
@@ -308,7 +309,8 @@ public class NewUserServlet extends ControlledPwmServlet {
         final Locale locale = pwmRequest.getLocale();
         final Locale locale = pwmRequest.getLocale();
 
 
         try {
         try {
-            final NewUserBean.NewUserForm newUserForm = NewUserFormUtils.readFromJsonRequest(pwmRequest);
+            final NewUserBean newUserBean = getNewUserBean(pwmRequest);
+            final NewUserForm newUserForm = NewUserFormUtils.readFromJsonRequest(pwmRequest, newUserBean);
             PasswordUtility.PasswordCheckInfo passwordCheckInfo = verifyForm(pwmRequest, newUserForm, true);
             PasswordUtility.PasswordCheckInfo passwordCheckInfo = verifyForm(pwmRequest, newUserForm, true);
             if (passwordCheckInfo.isPassed() && passwordCheckInfo.getMatch() == PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH) {
             if (passwordCheckInfo.isPassed() && passwordCheckInfo.getMatch() == PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH) {
                 passwordCheckInfo = new PasswordUtility.PasswordCheckInfo(
                 passwordCheckInfo = new PasswordUtility.PasswordCheckInfo(
@@ -336,7 +338,7 @@ public class NewUserServlet extends ControlledPwmServlet {
 
 
     static PasswordUtility.PasswordCheckInfo verifyForm(
     static PasswordUtility.PasswordCheckInfo verifyForm(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final NewUserBean.NewUserForm newUserForm,
+            final NewUserForm newUserForm,
             final boolean allowResultCaching
             final boolean allowResultCaching
     )
     )
             throws PwmDataValidationException, PwmUnrecoverableException, ChaiUnavailableException
             throws PwmDataValidationException, PwmUnrecoverableException, ChaiUnavailableException
@@ -359,15 +361,22 @@ public class NewUserServlet extends ControlledPwmServlet {
                 .cachedPasswordRuleAttributes(FormUtility.asStringMap(formValueData))
                 .cachedPasswordRuleAttributes(FormUtility.asStringMap(formValueData))
                 .passwordPolicy(newUserProfile.getNewUserPasswordPolicy(pwmApplication, locale))
                 .passwordPolicy(newUserProfile.getNewUserPasswordPolicy(pwmApplication, locale))
                 .build();
                 .build();
-        return PasswordUtility.checkEnteredPassword(
-                pwmApplication,
-                locale,
-                null,
-                uiBean,
-                null,
-                newUserForm.getNewUserPassword(),
-                newUserForm.getConfirmPassword()
-        );
+
+        final boolean promptForPassword = newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD);
+
+        if (promptForPassword) {
+            return PasswordUtility.checkEnteredPassword(
+                    pwmApplication,
+                    locale,
+                    null,
+                    uiBean,
+                    null,
+                    newUserForm.getNewUserPassword(),
+                    newUserForm.getConfirmPassword()
+            );
+        }
+
+        return new PasswordUtility.PasswordCheckInfo(null, true, 0, PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH, 0);
     }
     }
 
 
     @ActionHandler(action = "enterCode")
     @ActionHandler(action = "enterCode")
@@ -391,7 +400,7 @@ public class NewUserServlet extends ControlledPwmServlet {
                 final NewUserBean newUserBean = getNewUserBean(pwmRequest);
                 final NewUserBean newUserBean = getNewUserBean(pwmRequest);
                 final NewUserTokenData newUserTokenData = NewUserFormUtils.fromTokenPayload(pwmRequest, tokenPayload);
                 final NewUserTokenData newUserTokenData = NewUserFormUtils.fromTokenPayload(pwmRequest, tokenPayload);
                 newUserBean.setProfileID(newUserTokenData.getProfileID());
                 newUserBean.setProfileID(newUserTokenData.getProfileID());
-                final NewUserBean.NewUserForm newUserFormFromToken = newUserTokenData.getFormData();
+                final NewUserForm newUserFormFromToken = newUserTokenData.getFormData();
                 if (password.pwm.svc.token.TokenType.NEWUSER_EMAIL.matchesName(tokenPayload.getName())) {
                 if (password.pwm.svc.token.TokenType.NEWUSER_EMAIL.matchesName(tokenPayload.getName())) {
                     LOGGER.debug(pwmRequest, "email token passed");
                     LOGGER.debug(pwmRequest, "email token passed");
 
 
@@ -402,6 +411,7 @@ public class NewUserServlet extends ControlledPwmServlet {
                         throw e;
                         throw e;
                     }
                     }
 
 
+                    newUserBean.setRemoteInputData(newUserTokenData.getInjectionData());
                     newUserBean.setNewUserForm(newUserFormFromToken);
                     newUserBean.setNewUserForm(newUserFormFromToken);
                     newUserBean.setFormPassed(true);
                     newUserBean.setFormPassed(true);
                     newUserBean.getTokenVerificationProgress().getPassedTokens().add(TokenVerificationProgress.TokenChannel.EMAIL);
                     newUserBean.getTokenVerificationProgress().getPassedTokens().add(TokenVerificationProgress.TokenChannel.EMAIL);
@@ -479,7 +489,7 @@ public class NewUserServlet extends ControlledPwmServlet {
         newUserBean.setNewUserForm(null);
         newUserBean.setNewUserForm(null);
 
 
         try {
         try {
-            final NewUserBean.NewUserForm newUserForm = NewUserFormUtils.readFromRequest(pwmRequest, newUserBean);
+            final NewUserForm newUserForm = NewUserFormUtils.readFromRequest(pwmRequest, newUserBean);
             final PasswordUtility.PasswordCheckInfo passwordCheckInfo = verifyForm(pwmRequest, newUserForm, true);
             final PasswordUtility.PasswordCheckInfo passwordCheckInfo = verifyForm(pwmRequest, newUserForm, true);
             NewUserUtils.passwordCheckInfoToException(passwordCheckInfo);
             NewUserUtils.passwordCheckInfoToException(passwordCheckInfo);
             newUserBean.setNewUserForm(newUserForm);
             newUserBean.setNewUserForm(newUserForm);
@@ -616,10 +626,20 @@ public class NewUserServlet extends ControlledPwmServlet {
     private void forwardToFormPage(final PwmRequest pwmRequest, final NewUserBean newUserBean)
     private void forwardToFormPage(final PwmRequest pwmRequest, final NewUserBean newUserBean)
             throws ServletException, PwmUnrecoverableException, IOException
             throws ServletException, PwmUnrecoverableException, IOException
     {
     {
-        final List<FormConfiguration> formConfiguration = getFormDefinition(pwmRequest);
+        final List<FormConfiguration> formConfigurations = getFormDefinition(pwmRequest);
         final NewUserProfile newUserProfile = getNewUserProfile(pwmRequest);
         final NewUserProfile newUserProfile = getNewUserProfile(pwmRequest);
         final boolean promptForPassword = newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD);
         final boolean promptForPassword = newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD);
-        pwmRequest.addFormInfoToRequestAttr(formConfiguration, null, false, promptForPassword);
+        final Map<FormConfiguration,String> formData = new HashMap<>();
+        if (newUserBean.getRemoteInputData() != null) {
+            final Map<String,String> remoteData = newUserBean.getRemoteInputData();
+            for (final FormConfiguration formConfiguration : formConfigurations) {
+                if (remoteData.containsKey(formConfiguration.getName())) {
+                    formData.put(formConfiguration, remoteData.get(formConfiguration.getName()));
+                }
+            }
+        }
+
+        pwmRequest.addFormInfoToRequestAttr(formConfigurations, formData, false, promptForPassword);
 
 
         {
         {
             final boolean showBack = !newUserBean.isUrlSpecifiedProfile()
             final boolean showBack = !newUserBean.isUrlSpecifiedProfile()
@@ -629,5 +649,4 @@ public class NewUserServlet extends ControlledPwmServlet {
 
 
         pwmRequest.forwardToJsp(JspUrl.NEW_USER);
         pwmRequest.forwardToJsp(JspUrl.NEW_USER);
     }
     }
-
 }
 }

+ 14 - 7
src/main/java/password/pwm/http/servlet/newuser/NewUserTokenData.java

@@ -22,24 +22,31 @@
 
 
 package password.pwm.http.servlet.newuser;
 package password.pwm.http.servlet.newuser;
 
 
-import password.pwm.http.bean.NewUserBean;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
+import java.util.Map;
 
 
+@Getter
+@AllArgsConstructor
 class NewUserTokenData implements Serializable {
 class NewUserTokenData implements Serializable {
+
+    @SerializedName("id")
     private String profileID;
     private String profileID;
-    private NewUserBean.NewUserForm formData;
 
 
-    NewUserTokenData(final String profileID, final NewUserBean.NewUserForm formData) {
-        this.profileID = profileID;
-        this.formData = formData;
-    }
+    @SerializedName("f")
+    private NewUserForm formData;
+
+    @SerializedName("i")
+    private Map<String,String> injectionData;
 
 
     public String getProfileID() {
     public String getProfileID() {
         return profileID;
         return profileID;
     }
     }
 
 
-    public NewUserBean.NewUserForm getFormData() {
+    public NewUserForm getFormData() {
         return formData;
         return formData;
     }
     }
 }
 }

+ 20 - 9
src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -43,6 +43,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.option.TokenStorageMethod;
 import password.pwm.config.option.TokenStorageMethod;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.config.profile.NewUserProfile;
+import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
@@ -80,7 +81,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
-public class NewUserUtils {
+class NewUserUtils {
     private static PwmLogger LOGGER = password.pwm.util.logging.PwmLogger.forClass(NewUserUtils.class);
     private static PwmLogger LOGGER = password.pwm.util.logging.PwmLogger.forClass(NewUserUtils.class);
 
 
     private NewUserUtils() {
     private NewUserUtils() {
@@ -102,7 +103,7 @@ public class NewUserUtils {
     }
     }
 
 
     static void createUser(
     static void createUser(
-            final NewUserBean.NewUserForm newUserForm,
+            final NewUserForm newUserForm,
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
             final String newUserDN
             final String newUserDN
     )
     )
@@ -124,7 +125,17 @@ public class NewUserUtils {
         }
         }
 
 
         NewUserUtils.LOGGER.debug(pwmSession, "beginning createUser process for " + newUserDN);
         NewUserUtils.LOGGER.debug(pwmSession, "beginning createUser process for " + newUserDN);
-        final PasswordData userPassword = newUserForm.getNewUserPassword();
+
+        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
+        final boolean promptForPassword = newUserProfile.readSettingAsBoolean(PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD);
+
+        final PasswordData userPassword;
+        if (promptForPassword) {
+            userPassword = newUserForm.getNewUserPassword();
+        } else {
+            final PwmPasswordPolicy pwmPasswordPolicy = newUserProfile.getNewUserPasswordPolicy(pwmRequest.getPwmApplication(), pwmRequest.getLocale());
+            userPassword = RandomPasswordGenerator.createRandomPassword(pwmRequest.getSessionLabel(), pwmPasswordPolicy, pwmRequest.getPwmApplication());
+        }
 
 
         // set up the user creation attributes
         // set up the user creation attributes
         final Map<String, String> createAttributes = NewUserFormUtils.getLdapDataFromNewUserForm(NewUserServlet.getNewUserProfile(pwmRequest), newUserForm);
         final Map<String, String> createAttributes = NewUserFormUtils.getLdapDataFromNewUserForm(NewUserServlet.getNewUserProfile(pwmRequest), newUserForm);
@@ -152,7 +163,6 @@ public class NewUserUtils {
         }
         }
 
 
         final ChaiUser theUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
         final ChaiUser theUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
-        final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile(pwmRequest);
 
 
         final boolean useTempPw;
         final boolean useTempPw;
         {
         {
@@ -168,8 +178,9 @@ public class NewUserUtils {
             NewUserUtils.LOGGER.trace(pwmSession, "will use temporary password process for new user entry: " + newUserDN);
             NewUserUtils.LOGGER.trace(pwmSession, "will use temporary password process for new user entry: " + newUserDN);
             final PasswordData temporaryPassword;
             final PasswordData temporaryPassword;
             {
             {
-                final RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = new RandomPasswordGenerator.RandomGeneratorConfig();
-                randomGeneratorConfig.setPasswordPolicy(newUserProfile.getNewUserPasswordPolicy(pwmApplication, pwmRequest.getLocale()));
+                final RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = RandomPasswordGenerator.RandomGeneratorConfig.builder()
+                        .passwordPolicy(newUserProfile.getNewUserPasswordPolicy(pwmApplication, pwmRequest.getLocale()))
+                        .build();
                 temporaryPassword = RandomPasswordGenerator.createRandomPassword(pwmSession.getLabel(), randomGeneratorConfig, pwmApplication);
                 temporaryPassword = RandomPasswordGenerator.createRandomPassword(pwmSession.getLabel(), randomGeneratorConfig, pwmApplication);
             }
             }
             final ChaiUser proxiedUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
             final ChaiUser proxiedUser = ChaiFactory.createChaiUser(newUserDN, chaiProvider);
@@ -293,7 +304,7 @@ public class NewUserUtils {
 
 
     static String determineUserDN(
     static String determineUserDN(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final NewUserBean.NewUserForm formValues
+            final NewUserForm formValues
     )
     )
             throws PwmUnrecoverableException, ChaiUnavailableException
             throws PwmUnrecoverableException, ChaiUnavailableException
     {
     {
@@ -405,7 +416,7 @@ public class NewUserUtils {
     static MacroMachine createMacroMachineForNewUser(
     static MacroMachine createMacroMachineForNewUser(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
             final SessionLabel sessionLabel,
-            final NewUserBean.NewUserForm newUserForm
+            final NewUserForm newUserForm
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
@@ -445,7 +456,7 @@ public class NewUserUtils {
         }
         }
 
 
         final Configuration config = pwmApplication.getConfig();
         final Configuration config = pwmApplication.getConfig();
-        final Map<String, String> tokenPayloadMap = NewUserFormUtils.toTokenPayload(pwmRequest, newUserBean.getNewUserForm());
+        final Map<String, String> tokenPayloadMap = NewUserFormUtils.toTokenPayload(pwmRequest, newUserBean);
         final MacroMachine macroMachine = createMacroMachineForNewUser(pwmApplication, pwmRequest.getSessionLabel(), newUserBean.getNewUserForm());
         final MacroMachine macroMachine = createMacroMachineForNewUser(pwmApplication, pwmRequest.getSessionLabel(), newUserBean.getNewUserForm());
 
 
         switch (tokenType) {
         switch (tokenType) {

+ 2 - 2
src/main/java/password/pwm/http/state/SessionStateService.java

@@ -35,7 +35,7 @@ import password.pwm.svc.PwmService;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
-import java.util.Date;
+import java.time.Instant;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
@@ -179,7 +179,7 @@ public class SessionStateService implements PwmService {
         try {
         try {
             final E newBean = theClass.newInstance();
             final E newBean = theClass.newInstance();
             newBean.setGuid(sessionGuid);
             newBean.setGuid(sessionGuid);
-            newBean.setTimestamp(new Date());
+            newBean.setTimestamp(Instant.now());
             return newBean;
             return newBean;
         } catch (Exception e) {
         } catch (Exception e) {
             final String errorMsg = "unexpected error trying to instantiate bean class " + theClass.getName() + ": " + e.getMessage();
             final String errorMsg = "unexpected error trying to instantiate bean class " + theClass.getName() + ": " + e.getMessage();

+ 4 - 3
src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java

@@ -383,9 +383,10 @@ class LDAPAuthenticationRequest implements AuthenticationRequest {
             );
             );
 
 
             // create random password for user
             // create random password for user
-            final RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = new RandomPasswordGenerator.RandomGeneratorConfig();
-            randomGeneratorConfig.setSeedlistPhrases(RandomPasswordGenerator.DEFAULT_SEED_PHRASES);
-            randomGeneratorConfig.setPasswordPolicy(passwordPolicy);
+            final RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = RandomPasswordGenerator.RandomGeneratorConfig.builder()
+                    .seedlistPhrases(RandomPasswordGenerator.DEFAULT_SEED_PHRASES)
+                    .passwordPolicy(passwordPolicy)
+                    .build();
 
 
             final PasswordData currentPass = RandomPasswordGenerator.createRandomPassword(sessionLabel, randomGeneratorConfig, pwmApplication);
             final PasswordData currentPass = RandomPasswordGenerator.createRandomPassword(sessionLabel, randomGeneratorConfig, pwmApplication);
 
 

+ 4 - 1
src/main/java/password/pwm/svc/PwmServiceManager.java

@@ -34,6 +34,7 @@ import password.pwm.http.state.SessionStateService;
 import password.pwm.ldap.LdapConnectionService;
 import password.pwm.ldap.LdapConnectionService;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.cache.CacheService;
+import password.pwm.svc.cluster.ClusterService;
 import password.pwm.svc.event.AuditService;
 import password.pwm.svc.event.AuditService;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.report.ReportService;
 import password.pwm.svc.report.ReportService;
@@ -45,6 +46,7 @@ import password.pwm.svc.wordlist.SeedlistManager;
 import password.pwm.svc.wordlist.SharedHistoryManager;
 import password.pwm.svc.wordlist.SharedHistoryManager;
 import password.pwm.svc.wordlist.WordlistManager;
 import password.pwm.svc.wordlist.WordlistManager;
 import password.pwm.util.VersionChecker;
 import password.pwm.util.VersionChecker;
+import password.pwm.util.db.DatabaseService;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.operations.CrService;
 import password.pwm.util.operations.CrService;
@@ -72,7 +74,7 @@ public class PwmServiceManager {
     public enum PwmServiceClassEnum {
     public enum PwmServiceClassEnum {
         SecureService(          SecureService.class,             true),
         SecureService(          SecureService.class,             true),
         LdapConnectionService(  LdapConnectionService.class,     true),
         LdapConnectionService(  LdapConnectionService.class,     true),
-        DatabaseService(        password.pwm.util.db.DatabaseService.class,           true),
+        DatabaseService(        DatabaseService.class,           true),
         SharedHistoryManager(   SharedHistoryManager.class,      false),
         SharedHistoryManager(   SharedHistoryManager.class,      false),
         AuditService(           AuditService.class,              false),
         AuditService(           AuditService.class,              false),
         StatisticsManager(      StatisticsManager.class,         false),
         StatisticsManager(      StatisticsManager.class,         false),
@@ -93,6 +95,7 @@ public class PwmServiceManager {
         SessionTrackService(    SessionTrackService.class,       false),
         SessionTrackService(    SessionTrackService.class,       false),
         SessionStateSvc(        SessionStateService.class,       false),
         SessionStateSvc(        SessionStateService.class,       false),
         UserSearchEngine(       UserSearchEngine.class,          true),
         UserSearchEngine(       UserSearchEngine.class,          true),
+        ClusterService(         ClusterService.class,            false),
 
 
         ;
         ;
 
 

+ 35 - 0
src/main/java/password/pwm/svc/cluster/ClusterProvider.java

@@ -0,0 +1,35 @@
+/*
+ * 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.cluster;
+
+import password.pwm.error.PwmUnrecoverableException;
+
+import java.util.List;
+
+public interface ClusterProvider {
+    void close();
+
+    boolean isMaster();
+
+    List<NodeInfo> nodes() throws PwmUnrecoverableException;
+}

+ 106 - 0
src/main/java/password/pwm/svc/cluster/ClusterService.java

@@ -0,0 +1,106 @@
+/*
+ * 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.cluster;
+
+import password.pwm.PwmApplication;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.health.HealthRecord;
+import password.pwm.svc.PwmService;
+import password.pwm.util.logging.PwmLogger;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ClusterService implements PwmService {
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass(ClusterService.class);
+
+    private PwmApplication pwmApplication;
+    private STATUS status = STATUS.NEW;
+    private ClusterProvider clusterProvider;
+
+    @Override
+    public STATUS status()
+    {
+        return status;
+    }
+
+    @Override
+    public void init(final PwmApplication pwmApplication) throws PwmException
+    {
+        status = STATUS.OPENING;
+        this.pwmApplication = pwmApplication;
+
+        try {
+            if (this.pwmApplication.getConfig().hasDbConfigured()) {
+                clusterProvider = new DatabaseClusterProvider(pwmApplication);
+            }
+        } catch (PwmException e) {
+            LOGGER.error("error starting up cluster provider service: " + e.getMessage());
+            status = STATUS.CLOSED;
+            return;
+        }
+
+        status = STATUS.OPEN;
+    }
+
+    @Override
+    public void close()
+    {
+        if (clusterProvider != null) {
+            clusterProvider.close();
+            clusterProvider = null;
+        }
+        clusterProvider = null;
+        status = STATUS.CLOSED;
+    }
+
+    @Override
+    public List<HealthRecord> healthCheck()
+    {
+        return null;
+    }
+
+    @Override
+    public ServiceInfoBean serviceInfo()
+    {
+        return null;
+    }
+
+    public boolean isMaster() {
+        if (clusterProvider != null) {
+            return clusterProvider.isMaster();
+        }
+
+        return false;
+    }
+
+    public List<NodeInfo> nodes() throws PwmUnrecoverableException
+    {
+        if (status == STATUS.OPEN && clusterProvider != null) {
+            return clusterProvider.nodes();
+        }
+        return Collections.emptyList();
+    }
+}

+ 240 - 0
src/main/java/password/pwm/svc/cluster/DatabaseClusterProvider.java

@@ -0,0 +1,240 @@
+/*
+ * 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.cluster;
+
+import password.pwm.PwmApplication;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.db.DatabaseService;
+import password.pwm.util.db.DatabaseTable;
+import password.pwm.util.java.ClosableIterator;
+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.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+class DatabaseClusterProvider implements ClusterProvider {
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass(DatabaseClusterProvider.class);
+
+    private static final DatabaseTable TABLE = DatabaseTable.CLUSTER_STATE;
+
+
+    private static final String KEY_PREFIX_NODE = "node-";
+
+    private final PwmApplication pwmApplication;
+    private final DatabaseService databaseService;
+    private final ScheduledExecutorService executorService;
+
+    private ErrorInformation lastError;
+
+    private final Map<String,DatabaseStoredNodeData> nodeDatas = new ConcurrentHashMap<>();
+
+    private final DatabaseClusterSettings settings;
+
+    DatabaseClusterProvider(final PwmApplication pwmApplication) throws PwmUnrecoverableException
+    {
+        this.pwmApplication = pwmApplication;
+        this.settings = DatabaseClusterSettings.fromConfig(pwmApplication.getConfig());
+
+        if (!settings.isEnable()) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,"database clustering is not enabled via app property"));
+        }
+
+        this.databaseService = pwmApplication.getDatabaseService();
+        this.executorService = JavaHelper.makeSingleThreadExecutorService(pwmApplication, DatabaseClusterProvider.class);
+
+        final long intervalSeconds = settings.getHeartbeatInterval().getTotalSeconds();
+
+        this.executorService.scheduleAtFixedRate(
+                new HeartbeatProcess(),
+                1,
+                intervalSeconds,
+                TimeUnit.SECONDS
+        );
+    }
+
+    @Override
+    public void close() {
+        JavaHelper.closeAndWaitExecutor(executorService, new TimeDuration(1, TimeUnit.SECONDS));
+    }
+
+
+    @Override
+    public List<NodeInfo> nodes() throws PwmUnrecoverableException
+    {
+        final Map<String,NodeInfo> returnObj = new TreeMap<>();
+        final String configHash = pwmApplication.getConfig().configurationHash();
+        for (final DatabaseStoredNodeData storedNodeData : nodeDatas.values()) {
+            final boolean configMatch = configHash.equals(storedNodeData.getConfigHash());
+            final boolean timedOut = isTimedOut(storedNodeData);
+
+            final NodeInfo.NodeState nodeState = isMaster(storedNodeData)
+                    ? NodeInfo.NodeState.master
+                    : timedOut
+                    ? NodeInfo.NodeState.offline
+                    : NodeInfo.NodeState.online;
+
+            final Instant startupTime = nodeState == NodeInfo.NodeState.offline
+                    ? null
+                    : storedNodeData.getStartupTimestamp();
+
+
+            final NodeInfo nodeInfo = new NodeInfo(
+                    storedNodeData.getInstanceID(),
+                    storedNodeData.getTimestamp(),
+                    startupTime,
+                    nodeState,
+                    configMatch
+            );
+            returnObj.put(nodeInfo.getInstanceID(), nodeInfo);
+        }
+
+        return Collections.unmodifiableList(new ArrayList<>(returnObj.values()));
+    }
+
+
+    private String masterInstanceId() {
+        final List<DatabaseStoredNodeData> copiedDatas = new ArrayList<>(nodeDatas.values());
+        if (copiedDatas.isEmpty()) {
+            return null;
+        }
+
+        String masterID = null;
+        Instant eldestRecord = Instant.now();
+
+        for (final DatabaseStoredNodeData nodeData : copiedDatas) {
+            if (!isTimedOut(nodeData)) {
+                if (nodeData.getStartupTimestamp().isBefore(eldestRecord)) {
+                    eldestRecord = nodeData.getStartupTimestamp();
+                    masterID = nodeData.getInstanceID();
+                }
+            }
+        }
+        return masterID;
+    }
+
+    @Override
+    public boolean isMaster() {
+        final String myID = pwmApplication.getInstanceID();
+        final String masterID = masterInstanceId();
+        return myID.equals(masterID);
+    }
+
+    private boolean isMaster(final DatabaseStoredNodeData databaseStoredNodeData) {
+        final String masterID = masterInstanceId();
+        return databaseStoredNodeData.getInstanceID().equals(masterID);
+    }
+
+    private String dbKeyForStoredNode(final DatabaseStoredNodeData storedNodeData) throws PwmUnrecoverableException
+    {
+        final String instanceID = storedNodeData.getInstanceID();
+        final String hash = pwmApplication.getSecureService().hash(instanceID);
+        final String truncatedHash = hash.length() > 64
+                ? hash.substring(0, 64)
+                : hash;
+
+        return KEY_PREFIX_NODE + truncatedHash;
+    }
+
+    private boolean isTimedOut(final DatabaseStoredNodeData storedNodeData) {
+        final TimeDuration age = TimeDuration.fromCurrent(storedNodeData.getTimestamp());
+        return age.isLongerThan(settings.getNodeTimeout());
+    }
+
+    private class HeartbeatProcess implements Runnable {
+        public void run() {
+            writeNodeStatus();
+            readNodeStatuses();
+            purgeOutdatedNodes();
+        }
+
+        void writeNodeStatus()
+        {
+            try {
+                final DatabaseStoredNodeData storedNodeData = DatabaseStoredNodeData.makeNew(pwmApplication);
+                final String key = dbKeyForStoredNode(storedNodeData);
+                final String value = JsonUtil.serialize(storedNodeData);
+                databaseService.getAccessor().put(TABLE, key, value);
+            } catch (PwmException e) {
+                final String errorMsg = "error writing database cluster heartbeat: " + e.getMessage();
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg);
+                lastError = errorInformation;
+                LOGGER.error(lastError);
+            }
+        }
+
+        void readNodeStatuses()
+        {
+            try (ClosableIterator<String> tableIterator = databaseService.getAccessor().iterator(TABLE)) {
+                while (tableIterator.hasNext()) {
+                    final String dbKey = tableIterator.next();
+                    if (dbKey.startsWith(KEY_PREFIX_NODE)) {
+                        final String rawValueInDb = databaseService.getAccessor().get(TABLE, dbKey);
+                        final DatabaseStoredNodeData nodeDataInDb = JsonUtil.deserialize(rawValueInDb, DatabaseStoredNodeData.class);
+                        nodeDatas.put(nodeDataInDb.getInstanceID(), nodeDataInDb);
+                    }
+                }
+            } catch (PwmException e) {
+                final String errorMsg = "error reading database node statuses: " + e.getMessage();
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg);
+                lastError = errorInformation;
+                LOGGER.error(lastError);
+            }
+        }
+
+        void purgeOutdatedNodes() {
+            for (final DatabaseStoredNodeData storedNodeData : nodeDatas.values()) {
+                final TimeDuration recordAge = TimeDuration.fromCurrent(storedNodeData.getTimestamp());
+                final String instanceID = storedNodeData.getInstanceID();
+
+                if (recordAge.isLongerThan(settings.getNodePurgeInterval())) {
+                    // purge outdated records
+                    LOGGER.debug("purging outdated node reference to instanceID '" + instanceID + "'");
+
+                    try {
+                        databaseService.getAccessor().remove(TABLE, dbKeyForStoredNode(storedNodeData));
+                    } catch (PwmException e) {
+                        final String errorMsg = "error purging outdated node reference: " + e.getMessage();
+                        final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE, errorMsg);
+                        lastError = errorInformation;
+                        LOGGER.error(lastError);
+                    }
+                    nodeDatas.remove(instanceID);
+                }
+            }
+        }
+    }
+}

+ 50 - 0
src/main/java/password/pwm/svc/cluster/DatabaseClusterSettings.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.svc.cluster;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import password.pwm.AppProperty;
+import password.pwm.config.Configuration;
+import password.pwm.util.java.TimeDuration;
+
+import java.util.concurrent.TimeUnit;
+
+@Getter
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+class DatabaseClusterSettings {
+    private final boolean enable;
+    private final TimeDuration heartbeatInterval;
+    private final TimeDuration nodeTimeout;
+    private final TimeDuration nodePurgeInterval;
+
+    static DatabaseClusterSettings fromConfig(final Configuration configuration) {
+        return new DatabaseClusterSettings(
+                Boolean.parseBoolean(configuration.readAppProperty(AppProperty.CLUSTER_DB_ENABLE)),
+                new TimeDuration( Integer.parseInt(configuration.readAppProperty(AppProperty.CLUSTER_DB_HEARTBEAT_SECONDS)), TimeUnit.SECONDS),
+                new TimeDuration( Integer.parseInt(configuration.readAppProperty(AppProperty.CLUSTER_DB_NODE_TIMEOUT_SECONDS)), TimeUnit.SECONDS),
+                new TimeDuration( Integer.parseInt(configuration.readAppProperty(AppProperty.CLUSTER_DB_NODE_PURGE_SECONDS)), TimeUnit.SECONDS)
+        );
+    }
+}

+ 54 - 0
src/main/java/password/pwm/svc/cluster/DatabaseStoredNodeData.java

@@ -0,0 +1,54 @@
+/*
+ * 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.cluster;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import password.pwm.PwmApplication;
+import password.pwm.error.PwmUnrecoverableException;
+
+import java.io.Serializable;
+import java.time.Instant;
+
+@Getter
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+class DatabaseStoredNodeData implements Serializable {
+    private Instant timestamp;
+    private Instant startupTimestamp;
+    private String instanceID;
+    private String guid;
+    private String configHash;
+
+    static DatabaseStoredNodeData makeNew(final PwmApplication pwmApplication)
+            throws PwmUnrecoverableException
+    {
+        return new DatabaseStoredNodeData(
+                Instant.now(),
+                pwmApplication.getStartupTime(),
+                pwmApplication.getInstanceID(),
+                pwmApplication.getInstanceNonce(),
+                pwmApplication.getConfig().configurationHash()
+        );
+    }
+}

+ 18 - 13
src/main/java/password/pwm/util/db/DatabaseClusterService.java → src/main/java/password/pwm/svc/cluster/NodeInfo.java

@@ -20,22 +20,27 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
  */
 
 
-package password.pwm.util.db;
+package password.pwm.svc.cluster;
 
 
-public class DatabaseClusterService {
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
 
 
+import java.io.Serializable;
+import java.time.Instant;
 
 
+@Getter
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+public class NodeInfo implements Serializable {
+    private String instanceID;
+    private Instant lastSeen;
+    private Instant startupTime;
+    private NodeState nodeState;
+    private boolean configMatch;
 
 
-    private static final String KEY_ENGINE_START_PREFIX = "engine-start-";
-
-    private void heartbeat() {
-        /*
-        try {
-            put(DatabaseTable.PWM_META, KEY_ENGINE_START_PREFIX + instanceID, JavaHelper.toIsoDate(new java.util.Date()));
-        } catch (DatabaseException e) {
-            final String errorMsg = "error writing engine start time value: " + e.getMessage();
-            throw new DatabaseException(new ErrorInformation(PwmError.ERROR_DB_UNAVAILABLE,errorMsg));
-        }
-        */
+    enum NodeState {
+        master,
+        online,
+        offline
     }
     }
 }
 }

+ 4 - 13
src/main/java/password/pwm/svc/event/AuditService.java

@@ -214,7 +214,7 @@ public class AuditService implements PwmService {
         return userHistoryStore.readUserHistory(userInfoBean);
         return userHistoryStore.readUserHistory(userInfoBean);
     }
     }
 
 
-    protected void sendAsEmail(final AuditRecord record)
+    private void sendAsEmail(final AuditRecord record)
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         if (record == null || record.getEventCode() == null) {
         if (record == null || record.getEventCode() == null) {
@@ -227,14 +227,14 @@ public class AuditService implements PwmService {
         switch (record.getEventCode().getType()) {
         switch (record.getEventCode().getType()) {
             case SYSTEM:
             case SYSTEM:
                 for (final String toAddress : settings.getSystemEmailAddresses()) {
                 for (final String toAddress : settings.getSystemEmailAddresses()) {
-                    sendAsEmail(pwmApplication, null, record, toAddress, settings.getAlertFromAddress());
+                    sendAsEmail(pwmApplication, record, toAddress, settings.getAlertFromAddress());
                 }
                 }
                 break;
                 break;
 
 
             case USER:
             case USER:
             case HELPDESK:
             case HELPDESK:
                 for (final String toAddress : settings.getUserEmailAddresses()) {
                 for (final String toAddress : settings.getUserEmailAddresses()) {
-                    sendAsEmail(pwmApplication, null, record, toAddress, settings.getAlertFromAddress());
+                    sendAsEmail(pwmApplication, record, toAddress, settings.getAlertFromAddress());
                 }
                 }
                 break;
                 break;
 
 
@@ -246,7 +246,6 @@ public class AuditService implements PwmService {
 
 
     private static void sendAsEmail(
     private static void sendAsEmail(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
             final AuditRecord record,
             final AuditRecord record,
             final String toAddress,
             final String toAddress,
             final String fromAddress
             final String fromAddress
@@ -254,7 +253,7 @@ public class AuditService implements PwmService {
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication, sessionLabel);
+        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication, SessionLabel.AUDITING_SESSION_LABEL);
 
 
         String subject = macroMachine.expandMacros(pwmApplication.getConfig().readAppProperty(AppProperty.AUDIT_EVENTS_EMAILSUBJECT));
         String subject = macroMachine.expandMacros(pwmApplication.getConfig().readAppProperty(AppProperty.AUDIT_EVENTS_EMAILSUBJECT));
         subject = subject.replace("%EVENT%", record.getEventCode().getLocalizedString(pwmApplication.getConfig(), PwmConstants.DEFAULT_LOCALE));
         subject = subject.replace("%EVENT%", record.getEventCode().getLocalizedString(pwmApplication.getConfig(), PwmConstants.DEFAULT_LOCALE));
@@ -270,14 +269,6 @@ public class AuditService implements PwmService {
         pwmApplication.getEmailQueue().submitEmail(emailItem, null, macroMachine);
         pwmApplication.getEmailQueue().submitEmail(emailItem, null, macroMachine);
     }
     }
 
 
-    public int vaultSize() {
-        if (status != STATUS.OPEN || auditVault == null) {
-            return -1;
-        }
-
-        return auditVault.size();
-    }
-
     public Instant eldestVaultRecord() {
     public Instant eldestVaultRecord() {
         if (status != STATUS.OPEN || auditVault == null) {
         if (status != STATUS.OPEN || auditVault == null) {
             return null;
             return null;

+ 1 - 1
src/main/java/password/pwm/svc/event/SyslogAuditService.java

@@ -96,7 +96,7 @@ public class SyslogAuditService {
             syslogInstance = makeSyslogInstance(syslogConfig);
             syslogInstance = makeSyslogInstance(syslogConfig);
             LOGGER.trace("queued service running for " + syslogConfig);
             LOGGER.trace("queued service running for " + syslogConfig);
         } catch (IllegalArgumentException e) {
         } catch (IllegalArgumentException e) {
-            LOGGER.error("e9rror parsing syslog configuration for '" + syslogConfigString + "', error: " + e.getMessage());
+            LOGGER.error("error parsing syslog configuration for '" + syslogConfigString + "', error: " + e.getMessage());
         }
         }
 
 
         final WorkQueueProcessor.Settings settings = WorkQueueProcessor.Settings.builder()
         final WorkQueueProcessor.Settings settings = WorkQueueProcessor.Settings.builder()

+ 6 - 6
src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -331,7 +331,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService {
         if (storedValue != null) {
         if (storedValue != null) {
             return storedValue;
             return storedValue;
         }
         }
-        return new StoredWordlistDataBean.Builder().create();
+        return StoredWordlistDataBean.builder().build();
     }
     }
 
 
     void writeMetadata(final StoredWordlistDataBean metadataBean) {
     void writeMetadata(final StoredWordlistDataBean metadataBean) {
@@ -367,7 +367,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService {
         if (wlStatus == STATUS.OPEN) {
         if (wlStatus == STATUS.OPEN) {
             executorService.schedule(() -> {
             executorService.schedule(() -> {
                 try {
                 try {
-                    writeMetadata(new StoredWordlistDataBean.Builder().create());
+                    writeMetadata(StoredWordlistDataBean.builder().build());
                     populationManager.checkPopulation();
                     populationManager.checkPopulation();
                 } catch (Exception e) {
                 } catch (Exception e) {
                     LOGGER.error("error during clear operation: " + e.getMessage());
                     LOGGER.error("error during clear operation: " + e.getMessage());
@@ -407,7 +407,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService {
             } else {
             } else {
                 if (readMetadata().getSource() == StoredWordlistDataBean.Source.AutoImport) {
                 if (readMetadata().getSource() == StoredWordlistDataBean.Source.AutoImport) {
                     LOGGER.trace("source list is from auto-import, but not currently configured for auto-import; clearing stored data");
                     LOGGER.trace("source list is from auto-import, but not currently configured for auto-import; clearing stored data");
-                    writeMetadata(new StoredWordlistDataBean.Builder().create()); // clear previous auto-import wll
+                    writeMetadata(StoredWordlistDataBean.builder().build()); // clear previous auto-import wll
                 }
                 }
             }
             }
 
 
@@ -462,9 +462,9 @@ abstract class AbstractWordlist implements Wordlist, PwmService {
                 }
                 }
 
 
                 { // reset the wordlist metadata
                 { // reset the wordlist metadata
-                    final StoredWordlistDataBean storedWordlistDataBean = new StoredWordlistDataBean.Builder()
-                            .setSource(source)
-                            .create();
+                    final StoredWordlistDataBean storedWordlistDataBean = StoredWordlistDataBean.builder()
+                            .source(source)
+                            .build();
                     writeMetadata(storedWordlistDataBean);
                     writeMetadata(storedWordlistDataBean);
                 }
                 }
 
 

+ 9 - 8
src/main/java/password/pwm/svc/wordlist/Populator.java

@@ -39,6 +39,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
 import java.text.DecimalFormat;
 import java.text.DecimalFormat;
 import java.text.NumberFormat;
 import java.text.NumberFormat;
+import java.time.Instant;
 import java.util.Date;
 import java.util.Date;
 import java.util.Map;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.TreeMap;
@@ -139,7 +140,7 @@ class Populator {
 
 
     void populate() throws IOException, LocalDBException, PwmUnrecoverableException {
     void populate() throws IOException, LocalDBException, PwmUnrecoverableException {
         try {
         try {
-            rootWordlist.writeMetadata(new StoredWordlistDataBean.Builder().setSource(source).create());
+            rootWordlist.writeMetadata(StoredWordlistDataBean.builder().source(source).build());
             running = true;
             running = true;
             init();
             init();
 
 
@@ -240,13 +241,13 @@ class Populator {
         sb.append(" population complete, added ").append(wordlistSize);
         sb.append(" population complete, added ").append(wordlistSize);
         sb.append(" total words in ").append(new TimeDuration(overallStats.getElapsedSeconds() * 1000).asCompactString());
         sb.append(" total words in ").append(new TimeDuration(overallStats.getElapsedSeconds() * 1000).asCompactString());
         {
         {
-            final StoredWordlistDataBean storedWordlistDataBean = new StoredWordlistDataBean.Builder()
-                    .setSha1hash(JavaHelper.binaryArrayToHex(checksumInputStream.closeAndFinalChecksum()))
-                    .setSize(wordlistSize)
-                    .setStoreDate(new Date())
-                    .setSource(source)
-                    .setCompleted(!abortFlag)
-                    .create();
+            final StoredWordlistDataBean storedWordlistDataBean = StoredWordlistDataBean.builder()
+                    .sha1hash(JavaHelper.binaryArrayToHex(checksumInputStream.closeAndFinalChecksum()))
+                    .size(wordlistSize)
+                    .storeDate(Instant.now())
+                    .source(source)
+                    .completed(!abortFlag)
+                    .build();
             rootWordlist.writeMetadata(storedWordlistDataBean);
             rootWordlist.writeMetadata(storedWordlistDataBean);
         }
         }
         LOGGER.info(sb.toString());
         LOGGER.info(sb.toString());

+ 7 - 78
src/main/java/password/pwm/svc/wordlist/StoredWordlistDataBean.java

@@ -22,13 +22,18 @@
 
 
 package password.pwm.svc.wordlist;
 package password.pwm.svc.wordlist;
 
 
+import lombok.Builder;
+import lombok.Getter;
+
 import java.io.Serializable;
 import java.io.Serializable;
-import java.util.Date;
+import java.time.Instant;
 
 
+@Getter
+@Builder
 public class StoredWordlistDataBean implements Serializable {
 public class StoredWordlistDataBean implements Serializable {
     private boolean completed;
     private boolean completed;
     private Source source;
     private Source source;
-    private Date storeDate;
+    private Instant storeDate;
     private String sha1hash;
     private String sha1hash;
     private int size;
     private int size;
 
 
@@ -49,80 +54,4 @@ public class StoredWordlistDataBean implements Serializable {
             return label;
             return label;
         }
         }
     }
     }
-
-    public boolean isCompleted() {
-        return completed;
-    }
-
-    public Source getSource() {
-        return source;
-    }
-
-    public Date getStoreDate() {
-        return storeDate;
-    }
-
-    public String getSha1hash() {
-        return sha1hash;
-    }
-
-    public int getSize() {
-        return size;
-    }
-
-    public StoredWordlistDataBean(final boolean completed, final Source source, final Date storeDate, final String sha1hash, final int size) {
-        this.completed = completed;
-        this.source = source;
-        this.storeDate = storeDate;
-        this.sha1hash = sha1hash;
-        this.size = size;
-    }
-
-    public static class Builder {
-        private boolean completed;
-        private Source source;
-        private Date storeDate;
-        private String sha1hash;
-        private int size;
-
-        public Builder() {
-        }
-
-        public Builder(final StoredWordlistDataBean source) {
-            this.completed = source.completed;
-            this.source = source.source;
-            this.storeDate = source.storeDate;
-            this.sha1hash = source.sha1hash;
-            this.size = source.size;
-        }
-
-        public Builder setCompleted(final boolean completed) {
-            this.completed = completed;
-            return this;
-        }
-
-        public Builder setSource(final Source source) {
-            this.source = source;
-            return this;
-        }
-
-        public Builder setStoreDate(final Date storeDate) {
-            this.storeDate = storeDate;
-            return this;
-        }
-
-        public Builder setSha1hash(final String sha1hash) {
-            this.sha1hash = sha1hash;
-            return this;
-        }
-
-        public Builder setSize(final int size) {
-            this.size = size;
-            return this;
-        }
-
-        public StoredWordlistDataBean create() {
-            return new StoredWordlistDataBean(completed, source, storeDate, sha1hash, size);
-        }
-    }
 }
 }

+ 10 - 2
src/main/java/password/pwm/util/BasicAuthInfo.java

@@ -31,6 +31,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
+import javax.servlet.http.HttpServletRequest;
 import java.io.Serializable;
 import java.io.Serializable;
 
 
 /**
 /**
@@ -55,7 +56,14 @@ public class BasicAuthInfo implements Serializable {
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final PwmRequest pwmRequest
             final PwmRequest pwmRequest
     ) {
     ) {
-        final String authHeader = pwmRequest.readHeaderValueAsString(HttpHeader.Authorization);
+        return parseAuthHeader(pwmApplication, pwmRequest.getHttpServletRequest());
+    }
+
+    public static BasicAuthInfo parseAuthHeader(
+            final PwmApplication pwmApplication,
+            final HttpServletRequest httpServletRequest
+    ) {
+        final String authHeader = httpServletRequest.getHeader(HttpHeader.Authorization.getHttpName());
 
 
         if (authHeader != null) {
         if (authHeader != null) {
             if (authHeader.contains(PwmConstants.HTTP_BASIC_AUTH_PREFIX)) {
             if (authHeader.contains(PwmConstants.HTTP_BASIC_AUTH_PREFIX)) {
@@ -73,7 +81,7 @@ public class BasicAuthInfo implements Serializable {
                     //   "cn=user,o=company:chpass" or "user:chpass"
                     //   "cn=user,o=company:chpass" or "user:chpass"
                     return parseHeaderString(decoded);
                     return parseHeaderString(decoded);
                 } catch (Exception e) {
                 } catch (Exception e) {
-                    LOGGER.debug(pwmRequest, "error decoding auth header");
+                    LOGGER.debug("error decoding auth header");
                 }
                 }
             }
             }
         }
         }

+ 97 - 102
src/main/java/password/pwm/util/RandomPasswordGenerator.java

@@ -23,6 +23,8 @@
 package password.pwm.util;
 package password.pwm.util;
 
 
 import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException;
 import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException;
+import lombok.Builder;
+import lombok.Getter;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
@@ -100,8 +102,9 @@ public class RandomPasswordGenerator {
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final RandomGeneratorConfig randomGeneratorConfig = new RandomGeneratorConfig();
-        randomGeneratorConfig.setPasswordPolicy(passwordPolicy);
+        final RandomGeneratorConfig randomGeneratorConfig = RandomGeneratorConfig.builder()
+                .passwordPolicy(passwordPolicy)
+                .build();
 
 
         return createRandomPassword(
         return createRandomPassword(
                 sessionLabel,
                 sessionLabel,
@@ -135,28 +138,34 @@ public class RandomPasswordGenerator {
     {
     {
         final long startTimeMS = System.currentTimeMillis();
         final long startTimeMS = System.currentTimeMillis();
 
 
-        validateSettings(pwmApplication, randomGeneratorConfig);
+        randomGeneratorConfig.validateSettings(pwmApplication);
 
 
-        if (randomGeneratorConfig.getSeedlistPhrases() == null || randomGeneratorConfig.getSeedlistPhrases().isEmpty()) {
-            Set<String> seeds = DEFAULT_SEED_PHRASES;
-
-            final SeedlistManager seedlistManager = pwmApplication.getSeedlistManager();
-            if (seedlistManager != null && seedlistManager.status() == PwmService.STATUS.OPEN && seedlistManager.size() > 0) {
-                seeds = new HashSet<>();
-                int safetyCounter = 0;
-                while (seeds.size() < 10 && safetyCounter < 100) {
-                    safetyCounter++;
-                    final String randomWord = seedlistManager.randomSeed();
-                    if (randomWord != null) {
-                        seeds.add(randomWord);
+        final RandomGeneratorConfig effectiveConfig;
+        {
+            if (randomGeneratorConfig.getSeedlistPhrases() == null || randomGeneratorConfig.getSeedlistPhrases().isEmpty()) {
+                Set<String> seeds = DEFAULT_SEED_PHRASES;
+
+                final SeedlistManager seedlistManager = pwmApplication.getSeedlistManager();
+                if (seedlistManager != null && seedlistManager.status() == PwmService.STATUS.OPEN && seedlistManager.size() > 0) {
+                    seeds = new HashSet<>();
+                    int safetyCounter = 0;
+                    while (seeds.size() < 10 && safetyCounter < 100) {
+                        safetyCounter++;
+                        final String randomWord = seedlistManager.randomSeed();
+                        if (randomWord != null) {
+                            seeds.add(randomWord);
+                        }
                     }
                     }
                 }
                 }
+                effectiveConfig = randomGeneratorConfig.toBuilder()
+                        .seedlistPhrases(seeds)
+                        .build();
+            } else {
+                effectiveConfig = randomGeneratorConfig;
             }
             }
-            randomGeneratorConfig.setSeedlistPhrases(seeds);
         }
         }
 
 
-
-        final SeedMachine seedMachine = new SeedMachine(normalizeSeeds(randomGeneratorConfig.getSeedlistPhrases()));
+        final SeedMachine seedMachine = new SeedMachine(normalizeSeeds(effectiveConfig.getSeedlistPhrases()));
 
 
         int tryCount = 0;
         int tryCount = 0;
         final StringBuilder password = new StringBuilder();
         final StringBuilder password = new StringBuilder();
@@ -165,24 +174,24 @@ public class RandomPasswordGenerator {
         final PwmPasswordPolicy randomGenPolicy;
         final PwmPasswordPolicy randomGenPolicy;
         {
         {
             final Map<String, String> newPolicyMap = new HashMap<>();
             final Map<String, String> newPolicyMap = new HashMap<>();
-            newPolicyMap.putAll(randomGeneratorConfig.getPasswordPolicy().getPolicyMap());
+            newPolicyMap.putAll(effectiveConfig.getPasswordPolicy().getPolicyMap());
 
 
-            final String max = newPolicyMap.put(PwmPasswordRule.MaximumLength.getKey(), String.valueOf(randomGeneratorConfig.getMaximumLength()));
+            final String max = newPolicyMap.put(PwmPasswordRule.MaximumLength.getKey(), String.valueOf(effectiveConfig.getMaximumLength()));
 
 
-            if (randomGeneratorConfig.getMinimumLength() > randomGeneratorConfig.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MinimumLength)) {
-                newPolicyMap.put(PwmPasswordRule.MinimumLength.getKey(), String.valueOf(randomGeneratorConfig.getMinimumLength()));
+            if (effectiveConfig.getMinimumLength() > effectiveConfig.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MinimumLength)) {
+                newPolicyMap.put(PwmPasswordRule.MinimumLength.getKey(), String.valueOf(effectiveConfig.getMinimumLength()));
             }
             }
-            if (randomGeneratorConfig.getMaximumLength() < randomGeneratorConfig.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MaximumLength)) {
-                newPolicyMap.put(PwmPasswordRule.MaximumLength.getKey(), String.valueOf(randomGeneratorConfig.getMaximumLength()));
+            if (effectiveConfig.getMaximumLength() < effectiveConfig.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MaximumLength)) {
+                newPolicyMap.put(PwmPasswordRule.MaximumLength.getKey(), String.valueOf(effectiveConfig.getMaximumLength()));
             }
             }
-            if (randomGeneratorConfig.getMinimumStrength() > randomGeneratorConfig.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MinimumStrength)) {
-                newPolicyMap.put(PwmPasswordRule.MinimumStrength.getKey(), String.valueOf(randomGeneratorConfig.getMinimumStrength()));
+            if (effectiveConfig.getMinimumStrength() > effectiveConfig.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MinimumStrength)) {
+                newPolicyMap.put(PwmPasswordRule.MinimumStrength.getKey(), String.valueOf(effectiveConfig.getMinimumStrength()));
             }
             }
             randomGenPolicy = PwmPasswordPolicy.createPwmPasswordPolicy(newPolicyMap);
             randomGenPolicy = PwmPasswordPolicy.createPwmPasswordPolicy(newPolicyMap);
         }
         }
 
 
         // initial creation
         // initial creation
-        password.append(generateNewPassword(seedMachine, randomGeneratorConfig.getMinimumLength()));
+        password.append(generateNewPassword(seedMachine, effectiveConfig.getMinimumLength()));
 
 
         // read a rule validator
         // read a rule validator
         final PwmPasswordRuleValidator pwmPasswordRuleValidator = new PwmPasswordRuleValidator(pwmApplication, randomGenPolicy);
         final PwmPasswordRuleValidator pwmPasswordRuleValidator = new PwmPasswordRuleValidator(pwmApplication, randomGenPolicy);
@@ -197,7 +206,7 @@ public class RandomPasswordGenerator {
 
 
             if (tryCount % JITTER_COUNT == 0) {
             if (tryCount % JITTER_COUNT == 0) {
                 password.delete(0,password.length());
                 password.delete(0,password.length());
-                password.append(generateNewPassword(seedMachine, randomGeneratorConfig.getMinimumLength()));
+                password.append(generateNewPassword(seedMachine, effectiveConfig.getMinimumLength()));
             }
             }
 
 
             final List<ErrorInformation> errors = pwmPasswordRuleValidator.internalPwmPolicyValidator(
             final List<ErrorInformation> errors = pwmPasswordRuleValidator.internalPwmPolicyValidator(
@@ -208,7 +217,7 @@ public class RandomPasswordGenerator {
             } else if (checkPasswordAgainstDisallowedHttpValues(pwmApplication.getConfig(), password.toString())) {
             } else if (checkPasswordAgainstDisallowedHttpValues(pwmApplication.getConfig(), password.toString())) {
                 validPassword = false;
                 validPassword = false;
                 password.delete(0, password.length());
                 password.delete(0, password.length());
-                password.append(generateNewPassword(seedMachine, randomGeneratorConfig.getMinimumLength()));
+                password.append(generateNewPassword(seedMachine, effectiveConfig.getMinimumLength()));
             }
             }
         }
         }
 
 
@@ -544,99 +553,85 @@ public class RandomPasswordGenerator {
         return newSeeds.isEmpty() ? DEFAULT_SEED_PHRASES : newSeeds;
         return newSeeds.isEmpty() ? DEFAULT_SEED_PHRASES : newSeeds;
     }
     }
 
 
+    @Getter
+    @Builder(toBuilder = true)
     public static class RandomGeneratorConfig {
     public static class RandomGeneratorConfig {
-        public static final int DEFAULT_MINIMUM_LENGTH = 6;
-        public static final int DEFAULT_MAXIMUM_LENGTH = 16;
-        public static final int DEFAULT_DESIRED_STRENGTH = 45;
+        private static final int DEFAULT_MINIMUM_LENGTH = 6;
+        private static final int DEFAULT_MAXIMUM_LENGTH = 16;
+        private static final int DEFAULT_DESIRED_STRENGTH = 45;
 
 
-        public static final int MINIMUM_STRENGTH = 0;
-        public static final int MAXIMUM_STRENGTH = 100;
+        private static final int MINIMUM_STRENGTH = 0;
+        private static final int MAXIMUM_STRENGTH = 100;
 
 
+        /**
+         * A set of phrases (Strings) used to generate the RANDOM passwords.  There must be enough
+         * values in the phrases to build a random password that meets rule requirements
+         */
+        @Builder.Default
         private Collection<String> seedlistPhrases = Collections.emptySet();
         private Collection<String> seedlistPhrases = Collections.emptySet();
-        private int minimumLength = DEFAULT_MINIMUM_LENGTH;
-        private int maximumLength = DEFAULT_MAXIMUM_LENGTH;
-        private int minimumStrength = DEFAULT_DESIRED_STRENGTH;
-        private PwmPasswordPolicy passwordPolicy = PwmPasswordPolicy.defaultPolicy();
-
-        public Collection<String> getSeedlistPhrases() {
-            return seedlistPhrases;
-        }
 
 
         /**
         /**
-         * @param seedlistPhrases A set of phrases (Strings) used to generate the RANDOM passwords.  There must be enough
-         *                        values in the phrases to build a resonably RANDOM password that meets rule requirements
+         * The minimum length desired for the password.  The algorithm will attempt to make
+         * the returned value at least this long, but it is not guaranteed.
          */
          */
-        public void setSeedlistPhrases(final Collection<String> seedlistPhrases) {
-            this.seedlistPhrases = seedlistPhrases;
-        }
+        @Builder.Default
+        private int minimumLength = DEFAULT_MINIMUM_LENGTH;
 
 
-        public int getMinimumLength() {
-            return minimumLength;
-        }
+        @Builder.Default
+        private int maximumLength = DEFAULT_MAXIMUM_LENGTH;
 
 
         /**
         /**
-         * @param minimumLength The minimum length desired for the password.  The algorith will attempt to make
-         *                      the returned value at least this long, but it is not guarenteed.
+         * @param minimumStrength The minimum length desired strength.  The algorithm will attempt to make
+         *                        the returned value at least this strong, but it is not guaranteed.
          */
          */
-        public void setMinimumLength(final int minimumLength) {
-            this.minimumLength = minimumLength;
-        }
+        @Builder.Default
+        private int minimumStrength = DEFAULT_DESIRED_STRENGTH;
 
 
-        public int getMaximumLength() {
-            return maximumLength;
-        }
+        @Builder.Default
+        private PwmPasswordPolicy passwordPolicy = PwmPasswordPolicy.defaultPolicy();
 
 
-        public void setMaximumLength(final int maximumLength) {
-            this.maximumLength = maximumLength;
-        }
 
 
-        public int getMinimumStrength() {
-            return minimumStrength;
+        public int getMaximumLength() {
+            int policyMax = this.maximumLength;
+            if (this.getPasswordPolicy() != null) {
+                policyMax = this.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MaximumLength);
+            }
+            return Math.min(this.maximumLength, policyMax);
         }
         }
 
 
-        /**
-         * @param minimumStrength The minimum length desired strength.  The algorith will attempt to make
-         *                        the returned value at least this strong, but it is not guarenteed.
-         */
-        public void setMinimumStrength(final int minimumStrength) {
-            int desiredStrength = minimumStrength > MAXIMUM_STRENGTH ? MAXIMUM_STRENGTH : minimumStrength;
-            desiredStrength = desiredStrength < MINIMUM_STRENGTH ? MINIMUM_STRENGTH : desiredStrength;
-            this.minimumStrength = desiredStrength;
+        public int getMinimumStrength() {
+            int policyMin = this.minimumLength;
+            if (this.getPasswordPolicy() != null) {
+                policyMin = this.getPasswordPolicy().getRuleHelper().readIntValue(PwmPasswordRule.MinimumLength);
+            }
+            return Math.max(this.minimumLength, policyMin);
         }
         }
 
 
-        public PwmPasswordPolicy getPasswordPolicy() {
-            return passwordPolicy;
-        }
+        void validateSettings(final PwmApplication pwmApplication)
+                throws PwmUnrecoverableException
+        {
+            final int maxLength = Integer.parseInt(
+                    pwmApplication.getConfig().readAppProperty(AppProperty.PASSWORD_RANDOMGEN_MAX_LENGTH));
+            if (this.getMinimumLength() > maxLength) {
+                throw new PwmUnrecoverableException(new ErrorInformation(
+                        PwmError.ERROR_UNKNOWN,
+                        "minimum random generated password length exceeds preset random generator threshold"
+                ));
+            }
 
 
-        public void setPasswordPolicy(final PwmPasswordPolicy passwordPolicy) {
-            this.passwordPolicy = passwordPolicy;
-        }
-    }
+            if (this.getMaximumLength() > maxLength) {
+                throw new PwmUnrecoverableException(new ErrorInformation(
+                        PwmError.ERROR_UNKNOWN,
+                        "maximum random generated password length exceeds preset random generator threshold"
+                ));
+            }
 
 
-    public static void validateSettings(final PwmApplication pwmApplication, final RandomGeneratorConfig randomGeneratorConfig)
-            throws PwmUnrecoverableException
-    {
-        final int maxLength = Integer.parseInt(
-                pwmApplication.getConfig().readAppProperty(AppProperty.PASSWORD_RANDOMGEN_MAX_LENGTH));
-        if (randomGeneratorConfig.getMinimumLength() > maxLength) {
-            throw new PwmUnrecoverableException(new ErrorInformation(
-                    PwmError.ERROR_UNKNOWN,
-                    "minimum random generated password length exceeds preset random generator threshold"
-            ));
-        }
-
-        if (randomGeneratorConfig.getMaximumLength() > maxLength) {
-            throw new PwmUnrecoverableException(new ErrorInformation(
-                    PwmError.ERROR_UNKNOWN,
-                    "maximum random generated password length exceeds preset random generator threshold"
-            ));
-        }
-
-        if (randomGeneratorConfig.getMinimumStrength() > RandomGeneratorConfig.MAXIMUM_STRENGTH) {
-            throw new PwmUnrecoverableException(new ErrorInformation(
-                    PwmError.ERROR_UNKNOWN,
-                    "minimum random generated password strength exceeds maximum possible"
-            ));
+            if (this.getMinimumStrength() > RandomGeneratorConfig.MAXIMUM_STRENGTH) {
+                throw new PwmUnrecoverableException(new ErrorInformation(
+                        PwmError.ERROR_UNKNOWN,
+                        "minimum random generated password strength exceeds maximum possible"
+                ));
+            }
         }
         }
     }
     }
 }
 }

+ 2 - 2
src/main/java/password/pwm/util/cli/MainClass.java

@@ -27,12 +27,12 @@ import org.apache.log4j.EnhancedPatternLayout;
 import org.apache.log4j.Layout;
 import org.apache.log4j.Layout;
 import org.apache.log4j.Logger;
 import org.apache.log4j.Logger;
 import org.apache.log4j.varia.NullAppender;
 import org.apache.log4j.varia.NullAppender;
+import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmEnvironment;
 import password.pwm.PwmEnvironment;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -384,7 +384,7 @@ public class MainClass {
             throws Exception
             throws Exception
     {
     {
         final File databaseDirectory;
         final File databaseDirectory;
-        final String pwmDBLocationSetting = config.readSettingAsString(PwmSetting.PWMDB_LOCATION);
+        final String pwmDBLocationSetting = config.readAppProperty(AppProperty.LOCALDB_LOCATION);
         databaseDirectory = FileSystemUtility.figureFilepath(pwmDBLocationSetting, applicationPath);
         databaseDirectory = FileSystemUtility.figureFilepath(pwmDBLocationSetting, applicationPath);
         return LocalDBFactory.getInstance(databaseDirectory, readonly, null, config);
         return LocalDBFactory.getInstance(databaseDirectory, readonly, null, config);
     }
     }

+ 19 - 10
src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java

@@ -94,14 +94,18 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
                 processSqlException(debugInfo, e);
                 processSqlException(debugInfo, e);
             }
             }
 
 
-            final String sqlText;
-            if (!exists) {
-                sqlText = "INSERT INTO " + table.toString() + "(" + DatabaseService.KEY_COLUMN + ", " + DatabaseService.VALUE_COLUMN + ") VALUES(?,?)";
+            if (exists) {
+                final String sqlText = "UPDATE " + table.toString()
+                        + " SET " + DatabaseService.VALUE_COLUMN + "=? WHERE "
+                        + DatabaseService.KEY_COLUMN + "=?";
+                executeUpdate(sqlText, debugInfo, value, key); // note the value/key are reversed for this statement
             } else {
             } else {
-                sqlText = "UPDATE " + table.toString() + " SET " + DatabaseService.VALUE_COLUMN + "=? WHERE " + DatabaseService.KEY_COLUMN + "=?";
+                final String sqlText = "INSERT INTO " + table.toString()
+                        + "(" + DatabaseService.KEY_COLUMN + ", "
+                        + DatabaseService.VALUE_COLUMN + ") VALUES(?,?)";
+                executeUpdate(sqlText, debugInfo, key, value);
             }
             }
 
 
-            executeUpdate(sqlText, debugInfo, key, value);
             return !exists;
             return !exists;
         });
         });
     }
     }
@@ -223,7 +227,6 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
         return execute(debugInfo, () -> {
         return execute(debugInfo, () -> {
             final String sqlStatement = "SELECT COUNT(" + DatabaseService.KEY_COLUMN + ") FROM " + table.name();
             final String sqlStatement = "SELECT COUNT(" + DatabaseService.KEY_COLUMN + ") FROM " + table.name();
 
 
-
             try (PreparedStatement statement = connection.prepareStatement(sqlStatement)) {
             try (PreparedStatement statement = connection.prepareStatement(sqlStatement)) {
                 try (ResultSet resultSet = statement.executeQuery()) {
                 try (ResultSet resultSet = statement.executeQuery()) {
                     if (resultSet.next()) {
                     if (resultSet.next()) {
@@ -404,20 +407,26 @@ class DatabaseAccessorImpl implements DatabaseAccessor {
 
 
     private boolean containsImpl(final DatabaseTable table, final String key) throws SQLException
     private boolean containsImpl(final DatabaseTable table, final String key) throws SQLException
     {
     {
-        final String selectSql = "SELECT * FROM " + table.name() + " WHERE " + DatabaseService.KEY_COLUMN + " = ?";
-        try (PreparedStatement selectStatement = connection.prepareStatement(selectSql);) {
+        final String sqlStatement = "SELECT COUNT(" + DatabaseService.KEY_COLUMN + ") FROM " + table.name()
+                + " WHERE " + DatabaseService.KEY_COLUMN + " = ?";
+
+        try (PreparedStatement selectStatement = connection.prepareStatement(sqlStatement);) {
             selectStatement.setString(1, key);
             selectStatement.setString(1, key);
             selectStatement.setMaxRows(1);
             selectStatement.setMaxRows(1);
 
 
             try (ResultSet resultSet = selectStatement.executeQuery()) {
             try (ResultSet resultSet = selectStatement.executeQuery()) {
-                return resultSet.next();
+                if (resultSet.next()) {
+                    return resultSet.getInt(1) > 0;
+                }
             }
             }
         }
         }
+
+        return false;
     }
     }
 
 
     private void executeUpdate(final String sqlStatement, final DatabaseUtil.DebugInfo debugInfo, final String... params) throws DatabaseException
     private void executeUpdate(final String sqlStatement, final DatabaseUtil.DebugInfo debugInfo, final String... params) throws DatabaseException
     {
     {
-        try (PreparedStatement statement = connection.prepareStatement(sqlStatement)){
+        try (PreparedStatement statement = connection.prepareStatement(sqlStatement) ){
             for (int i = 0; i < params.length; i++) {
             for (int i = 0; i < params.length; i++) {
                 statement.setString(i + 1, params[i]);
                 statement.setString(i + 1, params[i]);
             }
             }

+ 1 - 0
src/main/java/password/pwm/util/db/DatabaseTable.java

@@ -30,4 +30,5 @@ public enum DatabaseTable {
     TOKENS,
     TOKENS,
     OTP,
     OTP,
     PW_NOTIFY,
     PW_NOTIFY,
+    CLUSTER_STATE,
 }
 }

+ 2 - 1
src/main/java/password/pwm/util/java/JavaHelper.java

@@ -57,6 +57,7 @@ import java.util.TimeZone;
 import java.util.TreeSet;
 import java.util.TreeSet;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.function.Predicate;
@@ -348,7 +349,7 @@ public class JavaHelper {
         return new CSVPrinter(new OutputStreamWriter(outputStream,PwmConstants.DEFAULT_CHARSET), PwmConstants.DEFAULT_CSV_FORMAT);
         return new CSVPrinter(new OutputStreamWriter(outputStream,PwmConstants.DEFAULT_CHARSET), PwmConstants.DEFAULT_CSV_FORMAT);
     }
     }
 
 
-    public static ExecutorService makeSingleThreadExecutorService(
+    public static ScheduledExecutorService makeSingleThreadExecutorService(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final Class clazz
             final Class clazz
     )
     )

+ 3 - 3
src/main/java/password/pwm/util/java/TimeDuration.java

@@ -52,7 +52,7 @@ public class TimeDuration implements Comparable, Serializable {
     public static final TimeDuration ZERO = new TimeDuration(0);
     public static final TimeDuration ZERO = new TimeDuration(0);
     public static final TimeDuration MILLISECOND = new TimeDuration(1, TimeUnit.MILLISECONDS);
     public static final TimeDuration MILLISECOND = new TimeDuration(1, TimeUnit.MILLISECONDS);
     public static final TimeDuration SECOND = new TimeDuration(1, TimeUnit.SECONDS);
     public static final TimeDuration SECOND = new TimeDuration(1, TimeUnit.SECONDS);
-    public static final TimeDuration SECONDS_10 = new TimeDuration(30, TimeUnit.SECONDS);
+    public static final TimeDuration SECONDS_10 = new TimeDuration(10, TimeUnit.SECONDS);
     public static final TimeDuration SECONDS_30 = new TimeDuration(30, TimeUnit.SECONDS);
     public static final TimeDuration SECONDS_30 = new TimeDuration(30, TimeUnit.SECONDS);
     public static final TimeDuration MINUTE = new TimeDuration(1, TimeUnit.MINUTES);
     public static final TimeDuration MINUTE = new TimeDuration(1, TimeUnit.MINUTES);
     public static final TimeDuration HOUR = new TimeDuration(1, TimeUnit.HOURS);
     public static final TimeDuration HOUR = new TimeDuration(1, TimeUnit.HOURS);
@@ -92,8 +92,8 @@ public class TimeDuration implements Comparable, Serializable {
         return new TimeDuration(System.currentTimeMillis(), instant.toEpochMilli());
         return new TimeDuration(System.currentTimeMillis(), instant.toEpochMilli());
     }
     }
 
 
-    public static String compactFromCurrent(final long ms) {
-        return new TimeDuration(System.currentTimeMillis(), ms).asCompactString();
+    public static String compactFromCurrent(final Instant instant) {
+        return TimeDuration.fromCurrent(instant).asCompactString();
     }
     }
 
 
     public static String asCompactString(final long ms) {
     public static String asCompactString(final long ms) {

+ 3 - 2
src/main/java/password/pwm/util/localdb/Derby_LocalDB.java

@@ -35,6 +35,7 @@ import java.sql.Connection;
 import java.sql.Driver;
 import java.sql.Driver;
 import java.sql.DriverManager;
 import java.sql.DriverManager;
 import java.sql.SQLException;
 import java.sql.SQLException;
+import java.time.Instant;
 import java.util.Map;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Properties;
 
 
@@ -142,7 +143,7 @@ public class Derby_LocalDB extends AbstractJDBC_LocalDB {
     }
     }
 
 
     private void reclaimAllSpace(final Connection dbConnection) {
     private void reclaimAllSpace(final Connection dbConnection) {
-        final java.util.Date startTime = new java.util.Date();
+        final Instant startTime = Instant.now();
         final long startSize = FileSystemUtility.getFileDirectorySize(dbDirectory);
         final long startSize = FileSystemUtility.getFileDirectorySize(dbDirectory);
         LOGGER.debug("beginning reclaim space in all tables startSize=" + StringUtil.formatDiskSize(startSize));
         LOGGER.debug("beginning reclaim space in all tables startSize=" + StringUtil.formatDiskSize(startSize));
         for (final LocalDB.DB db : LocalDB.DB.values()) {
         for (final LocalDB.DB db : LocalDB.DB.values()) {
@@ -150,7 +151,7 @@ public class Derby_LocalDB extends AbstractJDBC_LocalDB {
         }
         }
         final long completeSize = FileSystemUtility.getFileDirectorySize(dbDirectory);
         final long completeSize = FileSystemUtility.getFileDirectorySize(dbDirectory);
         final long sizeDifference = startSize - completeSize;
         final long sizeDifference = startSize - completeSize;
-        LOGGER.debug("completed reclaim space in all tables; duration=" + TimeDuration.fromCurrent(startTime).asCompactString()
+        LOGGER.debug("completed reclaim space in all tables; duration=" + TimeDuration.compactFromCurrent(startTime)
                 + ", startSize=" + StringUtil.formatDiskSize(startSize)
                 + ", startSize=" + StringUtil.formatDiskSize(startSize)
                 + ", completeSize=" + StringUtil.formatDiskSize(completeSize)
                 + ", completeSize=" + StringUtil.formatDiskSize(completeSize)
                 + ", sizeDifference=" + StringUtil.formatDiskSize(sizeDifference)
                 + ", sizeDifference=" + StringUtil.formatDiskSize(sizeDifference)

+ 8 - 40
src/main/java/password/pwm/ws/server/RestRequestBean.java

@@ -22,56 +22,24 @@
 
 
 package password.pwm.ws.server;
 package password.pwm.ws.server;
 
 
+import lombok.Getter;
+import lombok.Setter;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
+import password.pwm.config.option.WebServiceUsage;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
 
 
+@Getter
+@Setter
 public class RestRequestBean implements Serializable {
 public class RestRequestBean implements Serializable {
     private boolean authenticated;
     private boolean authenticated;
     private boolean external;
     private boolean external;
     private UserIdentity userIdentity;
     private UserIdentity userIdentity;
     private PwmSession pwmSession;
     private PwmSession pwmSession;
     private PwmApplication pwmApplication;
     private PwmApplication pwmApplication;
-
-    public boolean isAuthenticated() {
-        return authenticated;
-    }
-
-    public void setAuthenticated(final boolean authenticated) {
-        this.authenticated = authenticated;
-    }
-
-    public boolean isExternal() {
-        return external;
-    }
-
-    public void setExternal(final boolean external) {
-        this.external = external;
-    }
-
-    public UserIdentity getUserIdentity() {
-        return userIdentity;
-    }
-
-    public void setUserIdentity(final UserIdentity userIdentity) {
-        this.userIdentity = userIdentity;
-    }
-
-    public PwmSession getPwmSession() {
-        return pwmSession;
-    }
-
-    public void setPwmSession(final PwmSession pwmSession) {
-        this.pwmSession = pwmSession;
-    }
-
-    public PwmApplication getPwmApplication() {
-        return pwmApplication;
-    }
-
-    public void setPwmApplication(final PwmApplication pwmApplication) {
-        this.pwmApplication = pwmApplication;
-    }
+    private final Set<WebServiceUsage> webServiceUsages = new HashSet<>();
 }
 }

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

@@ -106,7 +106,7 @@ public class RestResultBean implements Serializable {
         final RestResultBean restResultBean = new RestResultBean();
         final RestResultBean restResultBean = new RestResultBean();
         restResultBean.setError(true);
         restResultBean.setError(true);
         restResultBean.setErrorMessage(errorInformation.toUserStr(locale, config));
         restResultBean.setErrorMessage(errorInformation.toUserStr(locale, config));
-        if (forceDetail || pwmApplication.determineIfDetailErrorMsgShown()) {
+        if (forceDetail || (pwmApplication != null && pwmApplication.determineIfDetailErrorMsgShown())) {
             restResultBean.setErrorDetail(errorInformation.toDebugStr());
             restResultBean.setErrorDetail(errorInformation.toDebugStr());
         }
         }
         restResultBean.setErrorCode(errorInformation.getError().getErrorCode());
         restResultBean.setErrorCode(errorInformation.getError().getErrorCode());

+ 7 - 20
src/main/java/password/pwm/ws/server/RestServerHelper.java

@@ -23,7 +23,6 @@
 package password.pwm.ws.server;
 package password.pwm.ws.server;
 
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
@@ -35,20 +34,19 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpHeader;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.http.filter.AuthenticationFilter;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.util.LocaleHelper;
 import password.pwm.util.LocaleHelper;
-import password.pwm.util.PasswordData;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response;
-import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URISyntaxException;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
@@ -56,9 +54,12 @@ import java.util.Locale;
 public abstract class RestServerHelper {
 public abstract class RestServerHelper {
     private static final PwmLogger LOGGER = PwmLogger.forClass(RestServerHelper.class);
     private static final PwmLogger LOGGER = PwmLogger.forClass(RestServerHelper.class);
 
 
-    public static javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
-        final URI uri = javax.ws.rs.core.UriBuilder.fromUri("../reference/rest.jsp?forwardedFromRestServer=true").build();
-        return javax.ws.rs.core.Response.temporaryRedirect(uri).build();
+    public static javax.ws.rs.core.Response handleHtmlRequest() throws URISyntaxException {
+        final String msg = "This REST service requires an appropriate "
+                + HttpHeader.Accept.getHttpName() + " header to be set.  This request used an html "
+                + HttpHeader.Accept.getHttpName() + " header, which is not supported.  See documentation at \"/public/reference/\".";
+        final String content = "<html><body>" + msg + "</body></html>";
+        return javax.ws.rs.core.Response.status(Response.Status.NOT_ACCEPTABLE).entity(content).build();
     }
     }
 
 
     public static RestRequestBean initializeRestRequest(
     public static RestRequestBean initializeRestRequest(
@@ -126,20 +127,6 @@ public abstract class RestServerHelper {
                     throw new PwmUnrecoverableException(errorInformation);
                     throw new PwmUnrecoverableException(errorInformation);
                 }
                 }
             }
             }
-
-            final PasswordData secretKey = pwmApplication.getConfig().readSettingAsPassword(PwmSetting.WEBSERVICES_EXTERNAL_SECRET);
-            if (secretKey != null) {
-                final String headerName = pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_WS_REST_SERVER_SECRET_HEADER);
-                final String headerValue = pwmRequest.readHeaderValueAsString(headerName);
-                if (headerValue == null || headerValue.isEmpty()) {
-                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, "request is missing security header " + headerName);
-                    throw new PwmUnrecoverableException(errorInformation);
-                }
-                if (!headerValue.equals(secretKey.getStringValue())) {
-                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, "authenticated user does not have correct security header");
-                    throw new PwmUnrecoverableException(errorInformation);
-                }
-            }
         }
         }
 
 
         final boolean adminPermission= restRequestBean.getPwmSession().getSessionManager().checkPermission(pwmApplication, Permission.PWMADMIN);
         final boolean adminPermission= restRequestBean.getPwmSession().getSessionManager().checkPermission(pwmApplication, Permission.PWMADMIN);

+ 89 - 0
src/main/java/password/pwm/ws/server/StandaloneRestHelper.java

@@ -0,0 +1,89 @@
+/*
+ * 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.ws.server;
+
+import com.novell.ldapchai.util.StringHelper;
+import password.pwm.PwmApplication;
+import password.pwm.config.NamedSecretData;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.option.WebServiceUsage;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ContextManager;
+import password.pwm.util.BasicAuthInfo;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class StandaloneRestHelper {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(StandaloneRestHelper.class);
+
+    public static StandaloneRestRequestBean initialize(final HttpServletRequest httpServletRequest)
+            throws PwmUnrecoverableException
+    {
+        final PwmApplication pwmApplication = ContextManager.getPwmApplication(httpServletRequest.getServletContext());
+
+        final Set<WebServiceUsage> usages = readWebServiceSecretAuthorizations(pwmApplication, httpServletRequest);
+
+        return StandaloneRestRequestBean.builder()
+                .pwmApplication(pwmApplication)
+                .authorizedUsages(Collections.unmodifiableSet(usages))
+                .build();
+    }
+
+    private static Set<WebServiceUsage> readWebServiceSecretAuthorizations(
+            final PwmApplication pwmApplication,
+            final HttpServletRequest httpServletRequest
+    )
+    {
+        final Set<WebServiceUsage> usages = new HashSet<>();
+
+        final BasicAuthInfo basicAuthInfo = BasicAuthInfo.parseAuthHeader(pwmApplication, httpServletRequest);
+        if (basicAuthInfo != null) {
+            final Map<String, NamedSecretData> secrets = pwmApplication.getConfig().readSettingAsNamedPasswords(PwmSetting.WEBSERVICES_EXTERNAL_SECRET);
+            final NamedSecretData namedSecretData = secrets.get(basicAuthInfo.getUsername());
+            if (namedSecretData != null) {
+                if (namedSecretData.getPassword().equals(basicAuthInfo.getPassword())) {
+                    final List<WebServiceUsage> namedSecrets = JavaHelper.readEnumListFromStringCollection(WebServiceUsage.class, namedSecretData.getUsage());
+                    usages.addAll(namedSecrets);
+                    LOGGER.trace("REST request to " + httpServletRequest.getRequestURI() + " specified a basic auth username (\""
+                            + basicAuthInfo.getUsername() + "\"), granting usage access to "
+                            + StringHelper.stringCollectionToString(namedSecretData.getUsage(), ","));
+                } else {
+                    LOGGER.trace("REST request to " + httpServletRequest.getRequestURI() + " specified a basic auth username (\""
+                            + basicAuthInfo.getUsername() + "\") with an incorrect password");
+                }
+            } else {
+                LOGGER.trace("REST request to " + httpServletRequest.getRequestURI() + " specified a basic auth username (\""
+                        + basicAuthInfo.getUsername() + "\") that does not correspond to a configured web secret");
+            }
+        }
+
+        return Collections.unmodifiableSet(usages);
+    }
+}

+ 37 - 0
src/main/java/password/pwm/ws/server/StandaloneRestRequestBean.java

@@ -0,0 +1,37 @@
+/*
+ * 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.ws.server;
+
+import lombok.Builder;
+import lombok.Getter;
+import password.pwm.PwmApplication;
+import password.pwm.config.option.WebServiceUsage;
+
+import java.util.Set;
+
+@Getter
+@Builder
+public class StandaloneRestRequestBean {
+    private final Set<WebServiceUsage> authorizedUsages;
+    private final PwmApplication pwmApplication;
+}

+ 1 - 1
src/main/java/password/pwm/ws/server/rest/RestAppDataServer.java

@@ -106,7 +106,7 @@ public class RestAppDataServer extends AbstractRestServer {
     @GET
     @GET
     @Produces(MediaType.TEXT_HTML)
     @Produces(MediaType.TEXT_HTML)
     public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
     public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
-        return RestServerHelper.doHtmlRedirect();
+        return RestServerHelper.handleHtmlRequest();
     }
     }
 
 
     @GET
     @GET

+ 1 - 1
src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java

@@ -144,7 +144,7 @@ public class RestChallengesServer extends AbstractRestServer {
     @GET
     @GET
     @Produces(MediaType.TEXT_HTML)
     @Produces(MediaType.TEXT_HTML)
     public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
     public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
-        return RestServerHelper.doHtmlRedirect();
+        return RestServerHelper.handleHtmlRequest();
     }
     }
 
 
     @GET
     @GET

+ 10 - 8
src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java

@@ -203,22 +203,23 @@ public class RestRandomPasswordServer extends AbstractRestServer {
     )
     )
             throws ChaiUnavailableException, PwmUnrecoverableException
             throws ChaiUnavailableException, PwmUnrecoverableException
     {
     {
-        final RandomPasswordGenerator.RandomGeneratorConfig randomConfig = new RandomPasswordGenerator.RandomGeneratorConfig();
+        final RandomPasswordGenerator.RandomGeneratorConfig.RandomGeneratorConfigBuilder randomConfigBuilder
+                = RandomPasswordGenerator.RandomGeneratorConfig.builder();
         if (jsonInput.strength > 0 && jsonInput.strength <= 100) {
         if (jsonInput.strength > 0 && jsonInput.strength <= 100) {
-            randomConfig.setMinimumStrength(jsonInput.strength);
+            randomConfigBuilder.minimumStrength(jsonInput.strength);
         }
         }
         if (jsonInput.minLength > 0 && jsonInput.minLength <= 100 * 1024) {
         if (jsonInput.minLength > 0 && jsonInput.minLength <= 100 * 1024) {
-            randomConfig.setMinimumLength(jsonInput.minLength);
+            randomConfigBuilder.minimumLength(jsonInput.minLength);
         }
         }
         if (jsonInput.maxLength > 0 && jsonInput.maxLength <= 100 * 1024) {
         if (jsonInput.maxLength > 0 && jsonInput.maxLength <= 100 * 1024) {
-            randomConfig.setMaximumLength(jsonInput.maxLength);
+            randomConfigBuilder.maximumLength(jsonInput.maxLength);
         }
         }
         if (jsonInput.chars != null) {
         if (jsonInput.chars != null) {
             final List<String> charValues = new ArrayList<>();
             final List<String> charValues = new ArrayList<>();
             for (int i = 0; i < jsonInput.chars.length(); i++) {
             for (int i = 0; i < jsonInput.chars.length(); i++) {
                 charValues.add(String.valueOf(jsonInput.chars.charAt(i)));
                 charValues.add(String.valueOf(jsonInput.chars.charAt(i)));
             }
             }
-            randomConfig.setSeedlistPhrases(charValues);
+            randomConfigBuilder.seedlistPhrases(charValues);
         }
         }
 
 
         if (!jsonInput.noUser && restRequestBean.getPwmSession().isAuthenticated()) {
         if (!jsonInput.noUser && restRequestBean.getPwmSession().isAuthenticated()) {
@@ -231,22 +232,23 @@ public class RestRandomPasswordServer extends AbstractRestServer {
                         ? restRequestBean.getPwmApplication().getProxiedChaiUser(restRequestBean.getUserIdentity())
                         ? restRequestBean.getPwmApplication().getProxiedChaiUser(restRequestBean.getUserIdentity())
                         : restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication(),userIdentity);
                         : restRequestBean.getPwmSession().getSessionManager().getActor(restRequestBean.getPwmApplication(),userIdentity);
 
 
-                randomConfig.setPasswordPolicy(PasswordUtility.readPasswordPolicyForUser(
+                randomConfigBuilder.passwordPolicy(PasswordUtility.readPasswordPolicyForUser(
                         restRequestBean.getPwmApplication(),
                         restRequestBean.getPwmApplication(),
                         restRequestBean.getPwmSession().getLabel(),
                         restRequestBean.getPwmSession().getLabel(),
                         restRequestBean.getUserIdentity(),
                         restRequestBean.getUserIdentity(),
                         theUser,
                         theUser,
                         restRequestBean.getPwmSession().getSessionStateBean().getLocale()));
                         restRequestBean.getPwmSession().getSessionStateBean().getLocale()));
             } else {
             } else {
-                randomConfig.setPasswordPolicy(restRequestBean.getPwmSession().getUserInfo().getPasswordPolicy());
+                randomConfigBuilder.passwordPolicy(restRequestBean.getPwmSession().getUserInfo().getPasswordPolicy());
             }
             }
         } else {
         } else {
             final Configuration config  = restRequestBean.getPwmApplication().getConfig();
             final Configuration config  = restRequestBean.getPwmApplication().getConfig();
-            randomConfig.setPasswordPolicy(config.getPasswordPolicy(
+            randomConfigBuilder.passwordPolicy(config.getPasswordPolicy(
                     config.getPasswordProfileIDs().iterator().next(),
                     config.getPasswordProfileIDs().iterator().next(),
                     restRequestBean.getPwmSession().getSessionStateBean().getLocale()));
                     restRequestBean.getPwmSession().getSessionStateBean().getLocale()));
         }
         }
 
 
+        final RandomPasswordGenerator.RandomGeneratorConfig randomConfig = randomConfigBuilder.build();
         final PasswordData randomPassword = RandomPasswordGenerator.createRandomPassword(restRequestBean.getPwmSession().getLabel(), randomConfig, restRequestBean.getPwmApplication());
         final PasswordData randomPassword = RandomPasswordGenerator.createRandomPassword(restRequestBean.getPwmSession().getLabel(), randomConfig, restRequestBean.getPwmApplication());
         final JsonOutput outputMap = new JsonOutput();
         final JsonOutput outputMap = new JsonOutput();
         outputMap.password = randomPassword.getStringValue();
         outputMap.password = randomPassword.getStringValue();

+ 15 - 14
src/main/java/password/pwm/ws/server/rest/RestSigningServer.java

@@ -24,16 +24,17 @@ package password.pwm.ws.server.rest;
 
 
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.Getter;
+import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
+import password.pwm.config.option.WebServiceUsage;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.secure.SecureService;
 import password.pwm.util.secure.SecureService;
-import password.pwm.ws.server.RestRequestBean;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.RestResultBean;
-import password.pwm.ws.server.RestServerHelper;
-import password.pwm.ws.server.ServicePermissions;
+import password.pwm.ws.server.StandaloneRestHelper;
+import password.pwm.ws.server.StandaloneRestRequestBean;
 
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.POST;
 import javax.ws.rs.POST;
@@ -48,12 +49,6 @@ import java.util.concurrent.TimeUnit;
 
 
 @Path("/signing")
 @Path("/signing")
 public class RestSigningServer extends AbstractRestServer {
 public class RestSigningServer extends AbstractRestServer {
-    private static final ServicePermissions SERVICE_PERMISSIONS = ServicePermissions.builder()
-            .adminOnly(false)
-            .authRequired(false)
-            .blockExternal(false)
-            .build();
-
 
 
     @POST
     @POST
     @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
     @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@@ -65,13 +60,19 @@ public class RestSigningServer extends AbstractRestServer {
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final RestRequestBean restRequestBean;
+        final StandaloneRestRequestBean restRequestBean;
         try {
         try {
-            restRequestBean = RestServerHelper.initializeRestRequest(request, response, SERVICE_PERMISSIONS, null);
+            restRequestBean = StandaloneRestHelper.initialize(request);
         } catch (PwmUnrecoverableException e) {
         } catch (PwmUnrecoverableException e) {
             return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
             return RestResultBean.fromError(e.getErrorInformation()).asJsonResponse();
         }
         }
 
 
+        if (!restRequestBean.getAuthorizedUsages().contains(WebServiceUsage.SigningForm)) {
+            final String errorMsg = "request is not authenticated with permission for SigningForm";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg);
+            return RestResultBean.fromError(errorInformation).asJsonResponse();
+        }
+
         try {
         try {
             if (inputFormData != null) {
             if (inputFormData != null) {
                 final SecureService securityService = restRequestBean.getPwmApplication().getSecureService();
                 final SecureService securityService = restRequestBean.getPwmApplication().getSecureService();
@@ -92,11 +93,12 @@ public class RestSigningServer extends AbstractRestServer {
 
 
     public static Map<String,String> readSignedFormValue(final PwmApplication pwmApplication, final String input) throws PwmUnrecoverableException
     public static Map<String,String> readSignedFormValue(final PwmApplication pwmApplication, final String input) throws PwmUnrecoverableException
     {
     {
-        final TimeDuration MAX_FORM_AGE = new TimeDuration(5, TimeUnit.MINUTES);
+        final Integer maxAgeSeconds = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.WS_REST_SERVER_SIGNING_FORM_TIMEOUT_SECONDS));
+        final TimeDuration maxAge = new TimeDuration(maxAgeSeconds, TimeUnit.SECONDS);
         final SignedFormData signedFormData = pwmApplication.getSecureService().decryptObject(input, SignedFormData.class);
         final SignedFormData signedFormData = pwmApplication.getSecureService().decryptObject(input, SignedFormData.class);
         if (signedFormData != null) {
         if (signedFormData != null) {
             if (signedFormData.getTimestamp() != null) {
             if (signedFormData.getTimestamp() != null) {
-                if (TimeDuration.fromCurrent(signedFormData.getTimestamp()).isLongerThan(MAX_FORM_AGE)) {
+                if (TimeDuration.fromCurrent(signedFormData.getTimestamp()).isLongerThan(maxAge)) {
                     throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SECURITY_VIOLATION,"signedForm data is too old"));
                     throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SECURITY_VIOLATION,"signedForm data is too old"));
                 }
                 }
 
 
@@ -112,5 +114,4 @@ public class RestSigningServer extends AbstractRestServer {
         private Instant timestamp;
         private Instant timestamp;
         private Map<String,String> formData;
         private Map<String,String> formData;
     }
     }
-
 }
 }

+ 1 - 1
src/main/java/password/pwm/ws/server/rest/RestStatusServer.java

@@ -57,7 +57,7 @@ public class RestStatusServer extends AbstractRestServer {
     @GET
     @GET
     @Produces(MediaType.TEXT_HTML)
     @Produces(MediaType.TEXT_HTML)
     public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
     public javax.ws.rs.core.Response doHtmlRedirect() throws URISyntaxException {
-        return RestServerHelper.doHtmlRedirect();
+        return RestServerHelper.handleHtmlRequest();
     }
     }
 
 
     @GET
     @GET

+ 1 - 1
src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java

@@ -61,7 +61,7 @@ public class RestVerifyOtpServer extends AbstractRestServer {
     @GET
     @GET
     @Produces(MediaType.TEXT_HTML)
     @Produces(MediaType.TEXT_HTML)
     public Response doHtmlRedirect() throws URISyntaxException {
     public Response doHtmlRedirect() throws URISyntaxException {
-        return RestServerHelper.doHtmlRedirect();
+        return RestServerHelper.handleHtmlRequest();
     }
     }
 
 
     @POST
     @POST

+ 1 - 1
src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java

@@ -90,7 +90,7 @@ public class RestVerifyResponsesServer extends AbstractRestServer {
     @GET
     @GET
     @Produces(MediaType.TEXT_HTML)
     @Produces(MediaType.TEXT_HTML)
     public Response doHtmlRedirect() throws URISyntaxException {
     public Response doHtmlRedirect() throws URISyntaxException {
-        return RestServerHelper.doHtmlRedirect();
+        return RestServerHelper.handleHtmlRequest();
     }
     }
 
 
     @POST
     @POST

+ 7 - 1
src/main/resources/password/pwm/AppProperty.properties

@@ -48,6 +48,10 @@ client.warningHeader.show=true
 client.pwShowRevertTimeout=45000
 client.pwShowRevertTimeout=45000
 client.js.enableHtml5Dialog=true
 client.js.enableHtml5Dialog=true
 client.jsp.showIcons=true
 client.jsp.showIcons=true
+cluster.db.enable=true
+cluster.db.heartbeatSeconds=60
+cluster.db.nodeTimeoutSeconds=600
+cluster.db.nodePurgeSeconds=86400
 config.reloadOnChange=true
 config.reloadOnChange=true
 config.maxJdbcJarSize=10240000
 config.maxJdbcJarSize=10240000
 config.maxPersistentLoginSeconds=3600
 config.maxPersistentLoginSeconds=3600
@@ -176,6 +180,7 @@ ldap.oracle.postTempPasswordUseCurrentTime=false
 localdb.aggressiveCompact.enabled=false
 localdb.aggressiveCompact.enabled=false
 localdb.implementation=password.pwm.util.localdb.Xodus_LocalDB
 localdb.implementation=password.pwm.util.localdb.Xodus_LocalDB
 localdb.initParameters=
 localdb.initParameters=
+localdb.location=LocalDB
 localdb.logWriter.bufferSize=500
 localdb.logWriter.bufferSize=500
 localdb.logWriter.maxBufferWaitMs=60000
 localdb.logWriter.maxBufferWaitMs=60000
 localdb.logWriter.maxTrimSize=5001
 localdb.logWriter.maxTrimSize=5001
@@ -248,7 +253,7 @@ security.input.trim=true
 security.input.password.trim=false
 security.input.password.trim=false
 security.input.themeMatchRegex=^[0-9a-zA-Z-_]*$
 security.input.themeMatchRegex=^[0-9a-zA-Z-_]*$
 security.ws.rest.clientKeyLength=32
 security.ws.rest.clientKeyLength=32
-security.ws.rest.server.secretKeyHeader=RestSecretKey
+security.ws.rest.server.secretKeyHeader=AuthorizationSecret
 security.sharedHistory.hashIterations=100000
 security.sharedHistory.hashIterations=100000
 security.sharedHistory.hashName=SHA-512
 security.sharedHistory.hashName=SHA-512
 security.sharedHistory.caseInsensitive=true
 security.sharedHistory.caseInsensitive=true
@@ -269,4 +274,5 @@ wordlist.builtin.path=/WEB-INF/wordlist.zip
 wordlist.maxCharLength=64
 wordlist.maxCharLength=64
 wordlist.minCharLength=2
 wordlist.minCharLength=2
 ws.restClient.pwRule.haltOnError=true
 ws.restClient.pwRule.haltOnError=true
+ws.restServer.signing.form.timeoutSeconds=120
 password.policy.allowMacroInRegexSetting=true
 password.policy.allowMacroInRegexSetting=true

+ 6 - 9
src/main/resources/password/pwm/config/PwmSetting.xml

@@ -2527,6 +2527,7 @@
         <ldapPermission actor="proxy" access="write"/>
         <ldapPermission actor="proxy" access="write"/>
         <flag>Form_ShowUniqueOption</flag>
         <flag>Form_ShowUniqueOption</flag>
         <flag>Form_ShowRequiredOption</flag>
         <flag>Form_ShowRequiredOption</flag>
+        <flag>Form_ShowReadOnlyOption</flag>
         <default>
         <default>
             <value>{"name":"mail","minimumLength":1,"maximumLength":64,"type":"email","required":true,"confirmationRequired":false,"readonly":false,"unique":true,"labels":{"":"Email Address"},"regexErrors":{"":"Email Address has invalid characters"},"description":{"":""},"placeholder":"username@example.com","selectOptions":{},regex:"^[a-zA-Z0-9 .,'@]*$"}</value>
             <value>{"name":"mail","minimumLength":1,"maximumLength":64,"type":"email","required":true,"confirmationRequired":false,"readonly":false,"unique":true,"labels":{"":"Email Address"},"regexErrors":{"":"Email Address has invalid characters"},"description":{"":""},"placeholder":"username@example.com","selectOptions":{},regex:"^[a-zA-Z0-9 .,'@]*$"}</value>
             <value>{"name":"givenName","minimumLength":1,"maximumLength":64,"type":"text","required":true,"confirmationRequired":false,"readonly":false,"labels":{"":"First Name"},"regexErrors":{"":""},"description":{"":""},"selectOptions":{},regex:"^[a-zA-Z0-9 .,'@]*$"}</value>
             <value>{"name":"givenName","minimumLength":1,"maximumLength":64,"type":"text","required":true,"confirmationRequired":false,"readonly":false,"labels":{"":"First Name"},"regexErrors":{"":""},"description":{"":""},"selectOptions":{},regex:"^[a-zA-Z0-9 .,'@]*$"}</value>
@@ -3562,10 +3563,11 @@
             <value>false</value>
             <value>false</value>
         </default>
         </default>
     </setting>
     </setting>
-    <setting hidden="false" key="webservices.external.secret" level="2">
-        <default>
-            <value/>
-        </default>
+    <setting hidden="false" key="webservices.external.secrets" level="2">
+        <default/>
+        <options>
+            <option value="SigningForm">Signing Form Service - /signing/form</option>
+        </options>
     </setting>
     </setting>
     <setting hidden="false" key="webservices.queryMatch" level="2">
     <setting hidden="false" key="webservices.queryMatch" level="2">
         <ldapPermission actor="proxy" access="read"/>
         <ldapPermission actor="proxy" access="read"/>
@@ -3677,11 +3679,6 @@
             <value>false</value>
             <value>false</value>
         </default>
         </default>
     </setting>
     </setting>
-    <setting hidden="true" key="pwmDb.location" level="2" required="true">
-        <default>
-            <value><![CDATA[LocalDB]]></value>
-        </default>
-    </setting>
     <!-- DEPRECATED SETTINGS -->
     <!-- DEPRECATED SETTINGS -->
     <category hidden="false" key="TEMPLATES">
     <category hidden="false" key="TEMPLATES">
     </category>
     </category>

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

@@ -241,7 +241,7 @@ Statistic_Description.HelpdeskTokenSent=Number of Help Desk verification tokens
 Statistic_Label.HelpdeskUnlock=Help Desk Unlocks
 Statistic_Label.HelpdeskUnlock=Help Desk Unlocks
 Statistic_Description.HelpdeskUnlock=Number of Help Desk unlock user events.
 Statistic_Description.HelpdeskUnlock=Number of Help Desk unlock user events.
 Statistic_Label.HelpdeskVerifyOTP=Help Desk OTP Verifications
 Statistic_Label.HelpdeskVerifyOTP=Help Desk OTP Verifications
-Statistic_Description.HelpdeskVerifyOTP=Number of successful Help Desk OPT verification user events.
+Statistic_Description.HelpdeskVerifyOTP=Number of successful Help Desk OTP verification user events.
 Statistic_Label.RestStatus=WebService Status Calls
 Statistic_Label.RestStatus=WebService Status Calls
 Statistic_Description.RestStatus=Number of external web service calls to the /status REST interface.
 Statistic_Description.RestStatus=Number of external web service calls to the /status REST interface.
 Statistic_Label.RestCheckPassword=WebService Check Password Calls
 Statistic_Label.RestCheckPassword=WebService Check Password Calls

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

@@ -570,7 +570,6 @@ Setting_Description_peopleSearch.searchFilter=Specify the LDAP search filter the
 Setting_Description_peopleSearch.useProxy=Enable this option to use the LDAP proxy account to perform searches. For proper security in most environments, do <b>not</b> enable this setting.
 Setting_Description_peopleSearch.useProxy=Enable this option to use the LDAP proxy account to perform searches. For proper security in most environments, do <b>not</b> enable this setting.
 Setting_Description_pwmAdmin.queryMatch=Specify the permissions @PwmAppName@ uses to determine if it grants a user administrator rights.
 Setting_Description_pwmAdmin.queryMatch=Specify the permissions @PwmAppName@ uses to determine if it grants a user administrator rights.
 Setting_Description_pwm.appProperty.overrides=(Troubleshooting only) Specify an override application properties value.  Do not use unless directed to by a support expert.
 Setting_Description_pwm.appProperty.overrides=(Troubleshooting only) Specify an override application properties value.  Do not use unless directed to by a support expert.
-Setting_Description_pwmDb.location=Specify the location of the LocalDB directory. If the specified path is relative, @PwmAppName@ considers the path relative to the servlet's <i>WEB-INF</i> directory.
 Setting_Description_pwm.forwardURL=Specify a URL that @PwmAppName@ forwards users to after the users complete any activity which does not require a log out.<br/><br/>You can override this setting for any given user session by adding a <i>forwardURL</i> parameter to any HTTP request. If blank, the system forwards the user to the @PwmAppName@ menu.
 Setting_Description_pwm.forwardURL=Specify a URL that @PwmAppName@ forwards users to after the users complete any activity which does not require a log out.<br/><br/>You can override this setting for any given user session by adding a <i>forwardURL</i> parameter to any HTTP request. If blank, the system forwards the user to the @PwmAppName@ menu.
 Setting_Description_pwm.homeURL=Specify the URL to redirect the user to upon clicking the home button. If blank, the home button returns the user to the application context URL.
 Setting_Description_pwm.homeURL=Specify the URL to redirect the user to upon clicking the home button. If blank, the home button returns the user to the application context URL.
 Setting_Description_pwmInstanceName=Specify the name of this application instance. If blank, @PwmAppName@ uses a persistent, randomly generated value. The recommended value is blank.
 Setting_Description_pwmInstanceName=Specify the name of this application instance. If blank, @PwmAppName@ uses a persistent, randomly generated value. The recommended value is blank.
@@ -674,7 +673,7 @@ Setting_Description_urlshortener.classname=Specify the URL Shortening Service cl
 Setting_Description_urlshortener.parameters=Specify the Name/Value settings used to configure the selected URL shortening service. For example, an API key, user name, password or domain name. The settings must be in "name\=value" format, where name is the key value of a valid service setting.
 Setting_Description_urlshortener.parameters=Specify the Name/Value settings used to configure the selected URL shortening service. For example, an API key, user name, password or domain name. The settings must be in "name\=value" format, where name is the key value of a valid service setting.
 Setting_Description_useXForwardedForHeader=If present, use the X-Forwarded-For HTTP header value as the client IP address instead of the source IP address of the HTTP connection.  Typically, upstream proxies add X-Forwarded-For headers or firewalls and might be the only reliable way to identify the user's source IP address.
 Setting_Description_useXForwardedForHeader=If present, use the X-Forwarded-For HTTP header value as the client IP address instead of the source IP address of the HTTP connection.  Typically, upstream proxies add X-Forwarded-For headers or firewalls and might be the only reliable way to identify the user's source IP address.
 Setting_Description_webservices.enableReadAnswers=Enable this option to allow @PwmAppName@ to use web services to read stored Challenge/Response answers of users.  The read responses are available in whatever hashing method format you selected.
 Setting_Description_webservices.enableReadAnswers=Enable this option to allow @PwmAppName@ to use web services to read stored Challenge/Response answers of users.  The read responses are available in whatever hashing method format you selected.
-Setting_Description_webservices.external.secret=If specified, @PwmAppName@ requires external web service clients to provide this value when invoking web services.
+Setting_Description_webservices.external.secrets=<p>Add named secrets for web services clients.  Protected web services require authentication with a named secret.  Create a secret with a name and secret key (password), and then enable the services that that named secret will be able to invoke.</p><p>Authentication to the web services must be invoked using Basic Authentication with the name of the secret as the username and the secret as the password.</p>
 Setting_Description_webservices.healthStats.makePublic=Enable this option to enable the Health and Statistics web services publicly.  Normally, these require authentication as there might be security sensitive data available.  Enabling this option allows the users to use the web services without authentication.  You must enable this is setting for the public (non-authenticated) page at <i>/public/health.jsp</i> to be functional.
 Setting_Description_webservices.healthStats.makePublic=Enable this option to enable the Health and Statistics web services publicly.  Normally, these require authentication as there might be security sensitive data available.  Enabling this option allows the users to use the web services without authentication.  You must enable this is setting for the public (non-authenticated) page at <i>/public/health.jsp</i> to be functional.
 Setting_Description_webservices.queryMatch=Add an LDAP filter that contains the users permitted to execute REST web services.
 Setting_Description_webservices.queryMatch=Add an LDAP filter that contains the users permitted to execute REST web services.
 Setting_Description_webservices.thirdParty.queryMatch=Add an LDAP filter that contains the users permitted to execute REST web services and specify a third party via the 'username' parameter.
 Setting_Description_webservices.thirdParty.queryMatch=Add an LDAP filter that contains the users permitted to execute REST web services and specify a third party via the 'username' parameter.
@@ -1043,7 +1042,6 @@ Setting_Label_peopleSearch.searchFilter=People Search LDAP Filter
 Setting_Label_peopleSearch.useProxy=Use Proxy Account
 Setting_Label_peopleSearch.useProxy=Use Proxy Account
 Setting_Label_pwmAdmin.queryMatch=Administrator Permission
 Setting_Label_pwmAdmin.queryMatch=Administrator Permission
 Setting_Label_pwm.appProperty.overrides=App Property Overrides
 Setting_Label_pwm.appProperty.overrides=App Property Overrides
-Setting_Label_pwmDb.location=LocalDB Location
 Setting_Label_pwm.forwardURL=Forward URL
 Setting_Label_pwm.forwardURL=Forward URL
 Setting_Label_pwm.homeURL=Home URL
 Setting_Label_pwm.homeURL=Home URL
 Setting_Label_pwmInstanceName=Instance Name
 Setting_Label_pwmInstanceName=Instance Name
@@ -1147,7 +1145,7 @@ Setting_Label_urlshortener.classname=Enable URL Shortening Service Class
 Setting_Label_urlshortener.parameters=Configuration Parameters for URL Shortening Service
 Setting_Label_urlshortener.parameters=Configuration Parameters for URL Shortening Service
 Setting_Label_useXForwardedForHeader=Use X-Forwarded-For Header
 Setting_Label_useXForwardedForHeader=Use X-Forwarded-For Header
 Setting_Label_webservices.enableReadAnswers=Allow Web Services to Read Answers
 Setting_Label_webservices.enableReadAnswers=Allow Web Services to Read Answers
-Setting_Label_webservices.external.secret=External Web Services Secret Key
+Setting_Label_webservices.external.secrets=External Web Services Secrets
 Setting_Label_webservices.healthStats.makePublic=Enable Public Health and Statistics Web Services
 Setting_Label_webservices.healthStats.makePublic=Enable Public Health and Statistics Web Services
 Setting_Label_webservices.queryMatch=External Web Services Permissions
 Setting_Label_webservices.queryMatch=External Web Services Permissions
 Setting_Label_webservices.thirdParty.queryMatch=Web Services Third Party Permissions
 Setting_Label_webservices.thirdParty.queryMatch=Web Services Third Party Permissions

+ 61 - 4
src/main/webapp/WEB-INF/jsp/admin-dashboard.jsp

@@ -28,6 +28,7 @@
 <%@ page import="password.pwm.i18n.Admin" %>
 <%@ page import="password.pwm.i18n.Admin" %>
 <%@ page import="password.pwm.i18n.Display" %>
 <%@ page import="password.pwm.i18n.Display" %>
 <%@ page import="password.pwm.svc.PwmService" %>
 <%@ page import="password.pwm.svc.PwmService" %>
+<%@ page import="password.pwm.svc.cluster.NodeInfo" %>
 <%@ page import="password.pwm.svc.sessiontrack.SessionTrackService" %>
 <%@ page import="password.pwm.svc.sessiontrack.SessionTrackService" %>
 <%@ page import="password.pwm.svc.stats.Statistic" %>
 <%@ page import="password.pwm.svc.stats.Statistic" %>
 <%@ page import="password.pwm.util.java.FileSystemUtility" %>
 <%@ page import="password.pwm.util.java.FileSystemUtility" %>
@@ -35,7 +36,8 @@
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="password.pwm.util.java.TimeDuration" %>
 <%@ page import="password.pwm.util.java.TimeDuration" %>
 <%@ page import="password.pwm.util.localdb.LocalDB" %>
 <%@ page import="password.pwm.util.localdb.LocalDB" %>
-<%@ page import="java.text.DateFormat" %>
+<%@ page import="java.lang.management.ManagementFactory" %>
+<%@ page import="java.lang.management.ThreadInfo" %>
 <%@ page import="java.text.NumberFormat" %>
 <%@ page import="java.text.NumberFormat" %>
 <%@ page import="java.time.Instant" %>
 <%@ page import="java.time.Instant" %>
 <%@ page import="java.util.Collection" %>
 <%@ page import="java.util.Collection" %>
@@ -43,9 +45,6 @@
 <%@ page import="java.util.List" %>
 <%@ page import="java.util.List" %>
 <%@ page import="java.util.Locale" %>
 <%@ page import="java.util.Locale" %>
 <%@ page import="java.util.Map" %>
 <%@ page import="java.util.Map" %>
-<%@ page import="java.util.TreeMap" %>
-<%@ page import="java.lang.management.ThreadInfo" %>
-<%@ page import="java.lang.management.ManagementFactory" %>
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true"
 <%@ page language="java" session="true" isThreadSafe="true"
          contentType="text/html" %>
          contentType="text/html" %>
@@ -733,6 +732,64 @@
                 </div>
                 </div>
                 <% } %>
                 <% } %>
             </div>
             </div>
+            <% if (dashboard_pwmApplication.getClusterService().status() == PwmService.STATUS.OPEN) { %>
+            <div id="Status" data-dojo-type="dijit.layout.ContentPane" title="Nodes" class="tabContent">
+                <div style="max-height: 400px; overflow: auto;">
+                    <table class="nomargin">
+                        <tr>
+                            <td style="font-weight:bold;">
+                                Instance ID
+                            </td>
+                            <td style="font-weight:bold;">
+                                Uptime
+                            </td>
+                            <td style="font-weight:bold;">
+                                Last Seen
+                            </td>
+                            <td style="font-weight:bold;">
+                                Master
+                            </td>
+                            <td style="font-weight:bold;">
+                                Config Match
+                            </td>
+                        </tr>
+                        <% for (final NodeInfo nodeInfo : dashboard_pwmApplication.getClusterService().nodes()) { %>
+                        <tr>
+                            <td>
+                                <%= nodeInfo.getInstanceID()  %>
+                            </td>
+                            <td>
+                                <% if (nodeInfo.getStartupTime() == null) { %>
+                                <pwm:display key="Value_NotApplicable"/>
+                                <% } else { %>
+                                <%= TimeDuration.fromCurrent(nodeInfo.getStartupTime()).asLongString(dashboard_pwmRequest.getLocale()) %>
+                                <% } %>
+                            </td>
+                            <td>
+                                <span class="timestamp">
+                                    <%= JspUtility.freindlyWrite(pageContext, nodeInfo.getLastSeen()) %>
+                                </span>
+                            </td>
+                            <td>
+                                <%= nodeInfo.getNodeState() %>
+                            </td>
+                            <td>
+                                <%= JspUtility.freindlyWrite(pageContext, nodeInfo.isConfigMatch())%>
+                            </td>
+                        </tr>
+                        <% } %>
+                    </table>
+                    <br/>
+                    <div class="footnote">
+                    <% if (dashboard_pwmApplication.getClusterService().isMaster()) { %>
+                    This node is the current master.
+                    <% } else { %>
+                    This node is not the current master.
+                    <% } %>
+                    </div>
+                </div>
+            </div>
+            <% } %>
         </div>
         </div>
     </div>
     </div>
     <div class="push"></div>
     <div class="push"></div>

+ 4 - 0
src/main/webapp/WEB-INF/jsp/fragment/form.jsp

@@ -13,6 +13,7 @@
 <%@ page import="java.util.Map" %>
 <%@ page import="java.util.Map" %>
 <%@ page import="password.pwm.http.tag.value.PwmValue" %>
 <%@ page import="password.pwm.http.tag.value.PwmValue" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
+<%@ page import="java.util.Collections" %>
 
 
 <%--
 <%--
   ~ Password Management Servlets (PWM)
   ~ Password Management Servlets (PWM)
@@ -50,6 +51,9 @@
     final boolean forceReadOnly = (Boolean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormReadOnly);
     final boolean forceReadOnly = (Boolean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormReadOnly);
     final boolean showPasswordFields = (Boolean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormShowPasswordFields);
     final boolean showPasswordFields = (Boolean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormShowPasswordFields);
     final Map<FormConfiguration,String> formDataMap = (Map<FormConfiguration,String>)JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormData);
     final Map<FormConfiguration,String> formDataMap = (Map<FormConfiguration,String>)JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormData);
+    final Map<String,String> initalValues = (JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormInitialValues) != null)
+            ? (Map<String,String>)JspUtility.getAttribute(pageContext, PwmRequestAttribute.FormInitialValues)
+            : Collections.<String,String>emptyMap();
 
 
     final PwmApplication pwmApplication = formPwmRequest.getPwmApplication();
     final PwmApplication pwmApplication = formPwmRequest.getPwmApplication();
     final Locale formLocale = formPwmRequest.getLocale();
     final Locale formLocale = formPwmRequest.getLocale();

+ 144 - 1
src/main/webapp/public/resources/js/configeditor-settings.js

@@ -2753,7 +2753,7 @@ SelectValueHandler.init = function(settingKey) {
 
 
     if (allowUserInput) {
     if (allowUserInput) {
         htmlBody += '<button class="btn" id="button_selectOverride_' + settingKey + '">'
         htmlBody += '<button class="btn" id="button_selectOverride_' + settingKey + '">'
-        + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Value</button>';
+            + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Value</button>';
 
 
     }
     }
 
 
@@ -3191,3 +3191,146 @@ PrivateKeyHandler.draw = function(keyName) {
         UILibrary.uploadFileDialog(options);
         UILibrary.uploadFileDialog(options);
     });
     });
 };
 };
+
+
+//--------- named secret handler ---
+var NamedSecretHandler = {};
+
+NamedSecretHandler.init = function(settingKey) {
+    var parentDiv = 'table_setting_' + settingKey;
+    var parentDivElement = PWM_MAIN.getObject(parentDiv);
+
+    if (parentDivElement) {
+        PWM_CFGEDIT.readSetting(settingKey,function(data){
+            PWM_VAR['clientSettingCache'][settingKey] = data;
+            var htmlBody = '';
+            htmlBody += '<table>';
+            var rowCounter = 0;
+            for (var key in data) {
+                var id = settingKey + '_' + key;
+                htmlBody += '<tr>';
+                htmlBody += '<td>' + key + '</td><td>Stored Value</td><td><button id="button-usage-' + id + '"><span class="btn-icon pwm-icon pwm-icon-sliders"/>Usage</button></td>';
+                htmlBody += '<td style="width:10px"><span class="delete-row-icon action-icon pwm-icon pwm-icon-times" id="button-deleteRow-' + id + '"></span></td>';
+                htmlBody += '</tr>';
+                rowCounter++;
+            }
+
+            if (rowCounter < 1) {
+                htmlBody += '<tr><td>No values.</td></tr>';
+            }
+
+            htmlBody += '</table>';
+
+            htmlBody += '<button id="button-addPassword-' + settingKey + '" class="btn"><span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Value</button>';
+            parentDivElement.innerHTML = htmlBody;
+
+            PWM_MAIN.addEventHandler('button-addPassword-' + settingKey,'click',function(){
+                NamedSecretHandler.addPassword(settingKey);
+            });
+
+            for (var key in data) {
+                var id = settingKey + '_' + key;
+                PWM_MAIN.addEventHandler('button-deleteRow-' + id,'click',function(){
+                    NamedSecretHandler.deletePassword(settingKey, key);
+                });
+                PWM_MAIN.addEventHandler('button-usage-' + id,'click',function(){
+                    NamedSecretHandler.usagePopup(settingKey, key);
+                });
+            }
+        });
+    }
+};
+
+NamedSecretHandler.usagePopup = function(settingKey, key) {
+    var titleText = PWM_SETTINGS['settings'][settingKey]['label'] + ' - Usage - ' + key ;
+    var options = PWM_SETTINGS['settings'][settingKey]['options'];
+    var currentValues = PWM_VAR['clientSettingCache'][settingKey];
+    var html = '<table class="noborder">';
+    for (var loopKey in options) {
+        (function (optionKey) {
+            html += '<tr><td>';
+            var buttonID = key + "_usage_button_" + optionKey;
+            html += '<label class="checkboxWrapper" style="min-width:180px;">'
+                + '<input type="checkbox" id="' + buttonID + '"/>'
+                + options[optionKey] + '</label>';
+            html += '</td></tr>';
+        })(loopKey);
+    }
+    html += '</table>';
+    var loadFunction = function () {
+        for (var loopKey in options) {
+            (function (optionKey) {
+                var buttonID = key + "_usage_button_" + optionKey;
+                var checked = PWM_MAIN.JSLibrary.arrayContains(currentValues[key]['usage'],optionKey);
+                PWM_MAIN.getObject(buttonID).checked = checked;
+                PWM_MAIN.addEventHandler(buttonID,'click',function(){
+                    var nowChecked = PWM_MAIN.getObject(buttonID).checked;
+                    if (nowChecked) {
+                        currentValues[key]['usage'].push(optionKey);
+                    } else {
+                        PWM_MAIN.JSLibrary.removeFromArray(currentValues[key]['usage'], optionKey);
+                    }
+                });
+            })(loopKey);
+        }
+    };
+    var okFunction = function() {
+        var postWriteFunction = function() {
+            NamedSecretHandler.init(settingKey);
+        };
+        PWM_CFGEDIT.writeSetting(settingKey, currentValues, postWriteFunction);
+    };
+    PWM_MAIN.showDialog({title:titleText,text:html,loadFunction:loadFunction,okAction:okFunction});
+};
+
+NamedSecretHandler.addPassword = function(settingKey) {
+    var titleText = PWM_SETTINGS['settings'][settingKey]['label'] + ' - Name';
+    var stringEditorFinishFunc = function(nameValue) {
+        var currentValues = PWM_VAR['clientSettingCache'][settingKey];
+        if (nameValue in currentValues) {;
+            var errorTxt = '"' + nameValue + '" already exists.';
+            PWM_MAIN.showErrorDialog(errorTxt);
+            return;
+        }
+        var pwDialogOptions = {};
+        pwDialogOptions['title'] = PWM_SETTINGS['settings'][settingKey]['label'] + ' - Password';
+        pwDialogOptions['showRandomGenerator'] = true;
+        pwDialogOptions['showValues'] = true;
+        pwDialogOptions['writeFunction'] = function(pwValue) {
+            currentValues[nameValue] = {};
+            currentValues[nameValue]['password'] = pwValue;
+            currentValues[nameValue]['usage'] = [];
+
+            var postWriteFunction = function() {
+                NamedSecretHandler.init(settingKey);
+            };
+
+            PWM_CFGEDIT.writeSetting(settingKey, currentValues, postWriteFunction);
+        };
+        UILibrary.passwordDialogPopup(pwDialogOptions)
+    };
+    var instructions = 'Please enter the name for the new password.';
+    UILibrary.stringEditorDialog({
+        title: titleText,
+        regex: '[a-zA-Z]{2,20}',
+        instructions: instructions,
+        completeFunction: stringEditorFinishFunc
+    });
+};
+
+
+NamedSecretHandler.deletePassword = function(settingKey, key) {
+    PWM_MAIN.showConfirmDialog({
+        text:'Delete named password <b>' + key + '</b>?',
+        okAction:function() {
+            var currentValues = PWM_VAR['clientSettingCache'][settingKey];
+            delete currentValues[key];
+
+            var postWriteFunction = function() {
+                NamedSecretHandler.init(settingKey);
+            };
+            PWM_CFGEDIT.writeSetting(settingKey, currentValues, postWriteFunction);
+        }
+    });
+
+};

+ 4 - 0
src/main/webapp/public/resources/js/configeditor.js

@@ -948,6 +948,10 @@ PWM_CFGEDIT.initSettingDisplay = function(setting, options) {
             ChangePasswordHandler.init(settingKey);
             ChangePasswordHandler.init(settingKey);
             break;
             break;
 
 
+        case 'NAMED_SECRET':
+            NamedSecretHandler.init(settingKey);
+            break;
+
         case 'NUMERIC':
         case 'NUMERIC':
             NumericValueHandler.init(settingKey);
             NumericValueHandler.init(settingKey);
             break;
             break;

+ 8 - 0
src/main/webapp/public/resources/js/main.js

@@ -1342,6 +1342,14 @@ PWM_MAIN.JSLibrary.arrayContains = function(array,element) {
     return array.indexOf(element) > -1;
     return array.indexOf(element) > -1;
 };
 };
 
 
+PWM_MAIN.JSLibrary.removeFromArray = function(array,element) {
+    for(var i = array.length - 1; i >= 0; i--) {
+        if(array[i] === element) {
+            array.splice(i, 1);
+        }
+    }
+};
+
 PWM_MAIN.toggleFullscreen = function(iconObj,divName) {
 PWM_MAIN.toggleFullscreen = function(iconObj,divName) {
     var obj = PWM_MAIN.getObject(divName);
     var obj = PWM_MAIN.getObject(divName);