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

configmanager-wordlist page
paged ldap search (via chai)
more servlet refactoring

jrivard 10 éve
szülő
commit
4a4a4aaecd
100 módosított fájl, 3475 hozzáadás és 2376 törlés
  1. 2 0
      pwm/servlet/src/password/pwm/AppProperty.java
  2. 3 1
      pwm/servlet/src/password/pwm/AppProperty.properties
  3. 12 12
      pwm/servlet/src/password/pwm/PwmApplication.java
  4. 2 1
      pwm/servlet/src/password/pwm/PwmConstants.java
  5. 0 1
      pwm/servlet/src/password/pwm/PwmConstants.properties
  6. 6 5
      pwm/servlet/src/password/pwm/config/Configuration.java
  7. 4 4
      pwm/servlet/src/password/pwm/config/PwmSetting.java
  8. 10 10
      pwm/servlet/src/password/pwm/config/PwmSetting.xml
  9. 2 1
      pwm/servlet/src/password/pwm/config/SettingUIFunction.java
  10. 2 2
      pwm/servlet/src/password/pwm/config/function/AbstractUriCertImportFunction.java
  11. 2 2
      pwm/servlet/src/password/pwm/config/function/LdapCertImportFunction.java
  12. 2 2
      pwm/servlet/src/password/pwm/config/function/SyslogCertImportFunction.java
  13. 7 3
      pwm/servlet/src/password/pwm/config/function/UserMatchViewerFunction.java
  14. 1 0
      pwm/servlet/src/password/pwm/config/profile/AbstractProfile.java
  15. 1 0
      pwm/servlet/src/password/pwm/config/profile/ChallengeProfile.java
  16. 2 4
      pwm/servlet/src/password/pwm/config/profile/ForgottenPasswordProfile.java
  17. 1 1
      pwm/servlet/src/password/pwm/config/profile/HelpdeskProfile.java
  18. 6 2
      pwm/servlet/src/password/pwm/config/profile/LdapProfile.java
  19. 1 1
      pwm/servlet/src/password/pwm/config/profile/NewUserProfile.java
  20. 261 252
      pwm/servlet/src/password/pwm/config/stored/ConfigurationReader.java
  21. 158 0
      pwm/servlet/src/password/pwm/config/stored/NGStoredConfiguration.java
  22. 11 0
      pwm/servlet/src/password/pwm/config/stored/StoredConfigReference.java
  23. 59 0
      pwm/servlet/src/password/pwm/config/stored/StoredConfigReferenceBean.java
  24. 35 0
      pwm/servlet/src/password/pwm/config/stored/StoredConfiguration.java
  25. 1562 1521
      pwm/servlet/src/password/pwm/config/stored/StoredConfigurationImpl.java
  26. 12 0
      pwm/servlet/src/password/pwm/config/stored/StoredConfigurationProvider.java
  27. 2 2
      pwm/servlet/src/password/pwm/config/value/UserPermissionValue.java
  28. 1 1
      pwm/servlet/src/password/pwm/health/LDAPStatusChecker.java
  29. 7 7
      pwm/servlet/src/password/pwm/http/ContextManager.java
  30. 7 8
      pwm/servlet/src/password/pwm/http/PwmRequest.java
  31. 5 1
      pwm/servlet/src/password/pwm/http/PwmSession.java
  32. 2 2
      pwm/servlet/src/password/pwm/http/SessionManager.java
  33. 0 38
      pwm/servlet/src/password/pwm/http/bean/AdminBean.java
  34. 5 5
      pwm/servlet/src/password/pwm/http/bean/ConfigGuideBean.java
  35. 4 4
      pwm/servlet/src/password/pwm/http/bean/ConfigManagerBean.java
  36. 4 56
      pwm/servlet/src/password/pwm/http/bean/LoginInfoBean.java
  37. 64 0
      pwm/servlet/src/password/pwm/http/bean/UserSessionDataCacheBean.java
  38. 235 0
      pwm/servlet/src/password/pwm/http/filter/ConfigAccessFilter.java
  39. 7 4
      pwm/servlet/src/password/pwm/http/filter/SessionFilter.java
  40. 40 0
      pwm/servlet/src/password/pwm/http/servlet/AccountInformationServlet.java
  41. 2 2
      pwm/servlet/src/password/pwm/http/servlet/ActivateUserServlet.java
  42. 41 36
      pwm/servlet/src/password/pwm/http/servlet/AdminServlet.java
  43. 23 19
      pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java
  44. 24 27
      pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java
  45. 1 1
      pwm/servlet/src/password/pwm/http/servlet/NewUserServlet.java
  46. 5 1
      pwm/servlet/src/password/pwm/http/servlet/PwmServletDefinition.java
  47. 14 2
      pwm/servlet/src/password/pwm/http/servlet/SetupOtpServlet.java
  48. 1 1
      pwm/servlet/src/password/pwm/http/servlet/SetupResponsesServlet.java
  49. 85 203
      pwm/servlet/src/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  50. 260 0
      pwm/servlet/src/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java
  51. 2 2
      pwm/servlet/src/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  52. 24 8
      pwm/servlet/src/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  53. 18 0
      pwm/servlet/src/password/pwm/http/servlet/peoplesearch/SearchResultBean.java
  54. 1 1
      pwm/servlet/src/password/pwm/http/servlet/resource/ResourceServletConfiguration.java
  55. 1 1
      pwm/servlet/src/password/pwm/http/tag/PwmIfTag.java
  56. 6 6
      pwm/servlet/src/password/pwm/i18n/Config.properties
  57. 2 2
      pwm/servlet/src/password/pwm/i18n/ConfigEditor.properties
  58. 1 0
      pwm/servlet/src/password/pwm/i18n/Display.java
  59. 1 0
      pwm/servlet/src/password/pwm/i18n/Display.properties
  60. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_cs.properties
  61. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_de.properties
  62. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_el.properties
  63. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_es.properties
  64. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_fi.properties
  65. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_fr.properties
  66. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_hu.properties
  67. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_ja.properties
  68. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_ko.properties
  69. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_nl.properties
  70. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_nn.properties
  71. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_no.properties
  72. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_pl.properties
  73. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_pt.properties
  74. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_pt_BR.properties
  75. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_sk.properties
  76. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_sv.properties
  77. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_th.properties
  78. 1 1
      pwm/servlet/src/password/pwm/i18n/Display_zh_TW.properties
  79. 1 1
      pwm/servlet/src/password/pwm/i18n/Health.properties
  80. 19 7
      pwm/servlet/src/password/pwm/ldap/LdapBrowser.java
  81. 3 0
      pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java
  82. 4 2
      pwm/servlet/src/password/pwm/ldap/UserSearchEngine.java
  83. 1 1
      pwm/servlet/src/password/pwm/ldap/auth/SessionAuthenticator.java
  84. 22 2
      pwm/servlet/src/password/pwm/util/TimeDuration.java
  85. 1 1
      pwm/servlet/src/password/pwm/util/X509Utils.java
  86. 1 1
      pwm/servlet/src/password/pwm/util/cli/CliEnvironment.java
  87. 5 5
      pwm/servlet/src/password/pwm/util/cli/ConfigLockCommand.java
  88. 4 4
      pwm/servlet/src/password/pwm/util/cli/ConfigNewCommand.java
  89. 3 3
      pwm/servlet/src/password/pwm/util/cli/ConfigSetPasswordCommand.java
  90. 5 5
      pwm/servlet/src/password/pwm/util/cli/ConfigUnlockCommand.java
  91. 1 1
      pwm/servlet/src/password/pwm/util/cli/MainClass.java
  92. 3 3
      pwm/servlet/src/password/pwm/util/cli/TokenInfoCommand.java
  93. 112 33
      pwm/servlet/src/password/pwm/util/localdb/Berkeley_LocalDB.java
  94. 45 0
      pwm/servlet/src/password/pwm/util/macro/StandardMacros.java
  95. 1 1
      pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java
  96. 36 16
      pwm/servlet/src/password/pwm/util/report/ReportService.java
  97. 1 1
      pwm/servlet/src/password/pwm/util/report/UserCacheService.java
  98. 96 0
      pwm/servlet/src/password/pwm/util/secure/ChecksumInputStream.java
  99. 28 0
      pwm/servlet/src/password/pwm/util/secure/HmacAlgorithm.java
  100. 5 5
      pwm/servlet/src/password/pwm/util/secure/PwmBlockAlgorithm.java

+ 2 - 0
pwm/servlet/src/password/pwm/AppProperty.java

@@ -207,12 +207,14 @@ public enum AppProperty {
     SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH         ("security.config.minSecurityKeyLength"),
     SECURITY_DEFAULT_EPHEMERAL_BLOCK_ALG            ("security.defaultEphemeralBlockAlg"),
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ("security.defaultEphemeralHashAlg"),
+    SEEDLIST_BUILTIN_PATH                           ("seedlist.builtin.path"),
     TOKEN_REMOVAL_DELAY_MS                          ("token.removalDelayMS"),
     TOKEN_PURGE_BATCH_SIZE                          ("token.purgeBatchSize"),
     TOKEN_MAX_UNIQUE_CREATE_ATTEMPTS                ("token.maxUniqueCreateAttempts"),
     
     /** Regular expression to be used for matching URLs to be shortened by the URL Shortening Service Class. */
     URL_SHORTNER_URL_REGEX                          ("urlshortener.url.regex"),
+    WORDLIST_BUILTIN_PATH                           ("wordlist.builtin.path"),
     WS_REST_CLIENT_PWRULE_HALTONERROR               ("ws.restClient.pwRule.haltOnError"),
 
     ;

+ 3 - 1
pwm/servlet/src/password/pwm/AppProperty.properties

@@ -174,7 +174,7 @@ queue.syslog.retryTimeoutMs=30000
 queue.syslog.maxAgeMs=86400000
 queue.syslog.maxCount=100000
 queue.maxCloseTimeoutMs=5000
-reporting.ldap.searchTimeoutMs=300000
+reporting.ldap.searchTimeoutMs=1800000
 recaptcha.clientJsUrl=//www.google.com/recaptcha/api.js
 recaptcha.clientIframeUrl=//www.google.com/recaptcha/api/noscript
 recaptcha.validateUrl=https://www.google.com/recaptcha/api/siteverify
@@ -195,8 +195,10 @@ security.ldap.canonicalCacheSeconds=30
 security.defaultEphemeralBlockAlg=AES128_HMAC256
 security.defaultEphemeralHashAlg=SHA512
 security.config.minSecurityKeyLength=32
+seedlist.builtin.path=seedlist.zip
 token.removalDelayMS=86400000
 token.purgeBatchSize=1000
 token.maxUniqueCreateAttempts=100
 urlshortener.url.regex=(https?://([^:@]+(:[^@]+)?@)?([a-zA-Z0-9.]+|d{1,3}.d{1,3}.d{1,3}.d{1,3}|[[0-9a-fA-F:]+])(:d{1,5})?/*[a-zA-Z0-9/\%_.]*?*[a-zA-Z0-9/\%_.=&#]*)
+wordlist.builtin.path=wordlist.zip
 ws.restClient.pwRule.haltOnError=true

+ 12 - 12
pwm/servlet/src/password/pwm/PwmApplication.java

@@ -98,6 +98,8 @@ public class PwmApplication {
         SMS_ITEM_COUNTER("smsQueue.itemCount"),
         EMAIL_ITEM_COUNTER("itemQueue.itemCount"),
         LOCALDB_IMPORT_STATUS("localDB.import.status"),
+        WORDLIST_METADATA("wordlist.metadata"),
+        SEEDLIST_METADATA("seedlist.metadata"),
 
         ;
 
@@ -222,7 +224,11 @@ public class PwmApplication {
         );
 
         if (!pwmEnvironment.internalRuntimeInstance) {
-            this.localDB = Initializer.initializeLocalDB(this);
+            if (getApplicationMode() == MODE.ERROR || getApplicationMode() == MODE.NEW) {
+                LOGGER.warn("skipping LocalDB open due to application mode " + getApplicationMode());
+            } else {
+                this.localDB = Initializer.initializeLocalDB(this);
+            }
         }
 
         this.localDBLogger = PwmLogManager.initializeLocalDBLogger(this);
@@ -295,7 +301,7 @@ public class PwmApplication {
     private void postInitTasks() {
         final Date startTime = new Date();
 
-        LOGGER.debug("loaded configuration: \n" + configuration.toDebugString());
+        LOGGER.debug("loaded configuration: " + configuration.toDebugString());
 
         // detect if config has been modified since previous startup
         try {
@@ -622,12 +628,7 @@ public class PwmApplication {
 
     private static class Initializer {
 
-        public static LocalDB initializeLocalDB(final PwmApplication pwmApplication) {
-            if (pwmApplication.getApplicationMode() == MODE.ERROR || pwmApplication.getApplicationMode() == MODE.NEW) {
-                LOGGER.warn("skipping LocalDB open due to application mode " + pwmApplication.getApplicationMode());
-                return null;
-            }
-
+        public static LocalDB initializeLocalDB(final PwmApplication pwmApplication) throws PwmUnrecoverableException {
             final File databaseDirectory;
             // see if META-INF isn't already there, then use WEB-INF.
             try {
@@ -636,7 +637,7 @@ public class PwmApplication {
             } catch (Exception e) {
                 pwmApplication.lastLocalDBFailure = new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,"error locating configured LocalDB directory: " + e.getMessage());
                 LOGGER.warn(pwmApplication.lastLocalDBFailure.toDebugStr());
-                return null;
+                throw new PwmUnrecoverableException(pwmApplication.lastLocalDBFailure);
             }
 
             LOGGER.debug("using localDB path " + databaseDirectory);
@@ -648,9 +649,8 @@ public class PwmApplication {
             } catch (Exception e) {
                 pwmApplication.lastLocalDBFailure = new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,"unable to initialize LocalDB: " + e.getMessage());
                 LOGGER.warn(pwmApplication.lastLocalDBFailure.toDebugStr());
+                throw new PwmUnrecoverableException(pwmApplication.lastLocalDBFailure);
             }
-
-            return null;
         }
     }
 
@@ -741,7 +741,7 @@ public class PwmApplication {
                     + "  An explicit applicationPath parameter must be specified, or the file can be removed if the applicationPath should be changed to the default /WEB-INF directory.";
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg));
         } else {
-            LOGGER.trace("marker file " + infoFile.getAbsolutePath() + " does not exist");
+            LOGGER.trace("marker file " + infoFile.getAbsolutePath() + " does not exist (this is usually a good thing, this file should not exist in a configured applicationPath");
         }
 
     }

+ 2 - 1
pwm/servlet/src/password/pwm/PwmConstants.java

@@ -97,7 +97,6 @@ public abstract class PwmConstants {
 
     public static final String APPLICATION_PATH_INFO_FILE = readPwmConstantsBundle("applicationPathInfoFile");
 
-    public static final int DEFAULT_WORDLIST_LOADFACTOR = Integer.parseInt(readPwmConstantsBundle("wordlist.loadFactor"));
     public static final int LOCALDB_LOGGER_MAX_QUEUE_SIZE = Integer.parseInt(readPwmConstantsBundle("pwmDBLoggerMaxQueueSize"));
     public static final int LOCALDB_LOGGER_MAX_DIRTY_BUFFER_MS = Integer.parseInt(readPwmConstantsBundle("pwmDBLoggerMaxDirtyBufferMS"));
     public static final boolean ENABLE_EULA_DISPLAY = Boolean.parseBoolean(readPwmConstantsBundle("enableEulaDisplay"));
@@ -234,12 +233,14 @@ public abstract class PwmConstants {
         GUEST_REGISTRATION("guest-create.jsp"),
         GUEST_UPDATE("guest-update.jsp"),
         GUEST_UPDATE_SEARCH("guest-search.jsp"),
+        ACCOUNT_INFORMATION("userinfo.jsp"),
         SHORTCUT("shortcut.jsp"),
         CAPTCHA("captcha.jsp"),
         PEOPLE_SEARCH("peoplesearch.jsp"),
         CONFIG_MANAGER_EDITOR("configeditor.jsp"),
         CONFIG_MANAGER_EDITOR_SUMMARY("configmanager-summary.jsp"),
         CONFIG_MANAGER_MODE_CONFIGURATION("configmanager.jsp"),
+        CONFIG_MANAGER_WORDLISTS("configmanager-wordlists.jsp"),
         CONFIG_MANAGER_LOGIN("configmanager-login.jsp"),
         HELPDESK_SEARCH("helpdesk.jsp"),
         HELPDESK_DETAIL("helpdesk-detail.jsp"),

+ 0 - 1
pwm/servlet/src/password/pwm/PwmConstants.properties

@@ -27,7 +27,6 @@
 locale.defaultLocale=en
 locale.defaultDateTimeFormat=yyyy-MM-dd'T'HH:mm:ss'Z'
 locale.defaultTimeZone=Zulu
-wordlist.loadFactor=50
 httpHeaderAuthorizationBasic=Basic
 httpHeaderXForwardedFor=X-Forwarded-For
 httpRestClientKey=X-RestClientKey

+ 6 - 5
pwm/servlet/src/password/pwm/config/Configuration.java

@@ -31,6 +31,7 @@ import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.TokenStorageMethod;
 import password.pwm.config.profile.*;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.*;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -56,13 +57,13 @@ public class Configuration implements Serializable, SettingReader {
 
     private final static PwmLogger LOGGER = PwmLogger.forClass(Configuration.class);
 
-    private final StoredConfiguration storedConfiguration;
+    private final StoredConfigurationImpl storedConfiguration;
 
     private DataCache dataCache = new DataCache();
 
     // --------------------------- CONSTRUCTORS ---------------------------
 
-    public Configuration(final StoredConfiguration storedConfiguration) {
+    public Configuration(final StoredConfigurationImpl storedConfiguration) {
         this.storedConfiguration = storedConfiguration;
     }
 
@@ -457,7 +458,7 @@ public class Configuration implements Serializable, SettingReader {
         return returnCollection;
     }
 
-    public String readProperty(final StoredConfiguration.ConfigProperty key) {
+    public String readProperty(final StoredConfigurationImpl.ConfigProperty key) {
         return storedConfiguration.readConfigProperty(key);
     }
 
@@ -481,7 +482,7 @@ public class Configuration implements Serializable, SettingReader {
     }
 
     public String getNotes() {
-        return storedConfiguration.readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_NOTES);
+        return storedConfiguration.readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_NOTES);
     }
 
     public PwmSecurityKey getSecurityKey() throws PwmUnrecoverableException {
@@ -800,7 +801,7 @@ public class Configuration implements Serializable, SettingReader {
 
     public Set<PwmSetting> nonDefaultSettings() {
         final HashSet returnSet = new HashSet();
-        for (StoredConfiguration.SettingValueRecord valueRecord : this.storedConfiguration.modifiedSettings()) {
+        for (StoredConfigurationImpl.SettingValueRecord valueRecord : this.storedConfiguration.modifiedSettings()) {
             returnSet.add(valueRecord.getSetting());
         }
         return returnSet;

+ 4 - 4
pwm/servlet/src/password/pwm/config/PwmSetting.java

@@ -140,8 +140,6 @@ public enum PwmSetting {
             "expireWarnTime", PwmSettingSyntax.DURATION, PwmSettingCategory.CHANGE_PASSWORD),
     EXPIRE_CHECK_DURING_AUTH(
             "expireCheckDuringAuth", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.CHANGE_PASSWORD),
-    SEEDLIST_FILENAME(
-            "pwm.seedlist.location", PwmSettingSyntax.STRING, PwmSettingCategory.CHANGE_PASSWORD),
     CHANGE_PASSWORD_WRITE_ATTRIBUTES(
             "changePassword.writeAttributes", PwmSettingSyntax.ACTION, PwmSettingCategory.CHANGE_PASSWORD),
     PASSWORD_SHOW_AUTOGEN(
@@ -327,8 +325,6 @@ public enum PwmSetting {
     //global password policy settings
     PASSWORD_POLICY_SOURCE(
             "password.policy.source", PwmSettingSyntax.SELECT, PwmSettingCategory.PASSWORD_GLOBAL),
-    WORDLIST_FILENAME(
-            "pwm.wordlist.location", PwmSettingSyntax.STRING, PwmSettingCategory.PASSWORD_GLOBAL),
     PASSWORD_SHAREDHISTORY_ENABLE(
             "password.sharedHistory.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PASSWORD_GLOBAL),
     PASSWORD_SHAREDHISTORY_MAX_AGE(
@@ -1020,6 +1016,10 @@ public enum PwmSetting {
 
 
     // deprecated.
+    WORDLIST_FILENAME(
+            "pwm.wordlist.location", PwmSettingSyntax.STRING, PwmSettingCategory.PASSWORD_GLOBAL),
+    SEEDLIST_FILENAME(
+            "pwm.seedlist.location", PwmSettingSyntax.STRING, PwmSettingCategory.CHANGE_PASSWORD),
     PASSWORD_POLICY_AD_COMPLEXITY(
             "password.policy.ADComplexity", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PASSWORD_POLICY),
     CHALLENGE_REQUIRE_RESPONSES(

+ 10 - 10
pwm/servlet/src/password/pwm/config/PwmSetting.xml

@@ -350,11 +350,6 @@
             <value>true</value>
         </default>
     </setting>
-    <setting key="pwm.seedlist.location" level="2">
-        <default>
-            <value><![CDATA[seedlist.zip]]></value>
-        </default>
-    </setting>
     <setting key="changePassword.writeAttributes" level="1">
         <default />
     </setting>
@@ -1096,11 +1091,6 @@
             <value><![CDATA[.*[^A-Za-z0-9]]]></value>
         </default>
     </setting>
-    <setting key="pwm.wordlist.location" level="1">
-        <default>
-            <value><![CDATA[wordlist.zip]]></value>
-        </default>
-    </setting>
     <setting key="wordlistCaseSensitive" level="1" required="true">
         <default>
             <value>false</value>
@@ -3123,6 +3113,16 @@
         </options>
     </setting>
     <!-- DEPRECATED SETTINGS -->
+    <setting key="pwm.wordlist.location" level="1" hidden="true">
+        <default>
+            <value><![CDATA[wordlist.zip]]></value>
+        </default>
+    </setting>
+    <setting key="pwm.seedlist.location" level="2" hidden="true">
+        <default>
+            <value><![CDATA[seedlist.zip]]></value>
+        </default>
+    </setting>
     <setting key="password.policy.ADComplexity" level="99" required="false" hidden="true">
         <default>
             <value>false</value>

+ 2 - 1
pwm/servlet/src/password/pwm/config/SettingUIFunction.java

@@ -22,6 +22,7 @@
 
 package password.pwm.config;
 
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.http.PwmRequest;
 
 import java.io.Serializable;
@@ -29,7 +30,7 @@ import java.io.Serializable;
 public interface SettingUIFunction {
     Serializable provideFunction(
             final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationImpl storedConfiguration,
             final PwmSetting setting,
             final String profile
     )

+ 2 - 2
pwm/servlet/src/password/pwm/config/function/AbstractUriCertImportFunction.java

@@ -26,7 +26,7 @@ import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.*;
 import password.pwm.http.PwmRequest;
@@ -44,7 +44,7 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction {
     @Override
     public String provideFunction(
             PwmRequest pwmRequest,
-            StoredConfiguration storedConfiguration,
+            StoredConfigurationImpl storedConfiguration,
             PwmSetting setting,
             String profile
     )

+ 2 - 2
pwm/servlet/src/password/pwm/config/function/LdapCertImportFunction.java

@@ -26,7 +26,7 @@ import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.*;
@@ -47,7 +47,7 @@ public class LdapCertImportFunction implements SettingUIFunction {
     @Override
     public String provideFunction(
             PwmRequest pwmRequest,
-            StoredConfiguration storedConfiguration,
+            StoredConfigurationImpl storedConfiguration,
             PwmSetting setting,
             String profile
     )

+ 2 - 2
pwm/servlet/src/password/pwm/config/function/SyslogCertImportFunction.java

@@ -26,7 +26,7 @@ import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.*;
 import password.pwm.event.SyslogAuditService;
@@ -45,7 +45,7 @@ public class SyslogCertImportFunction implements SettingUIFunction {
     @Override
     public String provideFunction(
             PwmRequest pwmRequest,
-            StoredConfiguration storedConfiguration,
+            StoredConfigurationImpl storedConfiguration,
             PwmSetting setting,
             String profile
     )

+ 7 - 3
pwm/servlet/src/password/pwm/config/function/UserMatchViewerFunction.java

@@ -29,7 +29,11 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.*;
+import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.SettingUIFunction;
+import password.pwm.config.UserPermission;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -47,7 +51,7 @@ public class UserMatchViewerFunction implements SettingUIFunction {
     @Override
     public Serializable provideFunction(
             PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationImpl storedConfiguration,
             final PwmSetting setting,
             final String profile
     )
@@ -67,7 +71,7 @@ public class UserMatchViewerFunction implements SettingUIFunction {
     public Collection<UserIdentity> discoverMatchingUsers(
             final PwmApplication pwmApplication,
             final int maxResultSize,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationImpl storedConfiguration,
             final PwmSetting setting,
             final String profile
     )

+ 1 - 0
pwm/servlet/src/password/pwm/config/profile/AbstractProfile.java

@@ -23,6 +23,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.config.*;
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.util.PasswordData;
 
 import java.security.cert.X509Certificate;

+ 1 - 0
pwm/servlet/src/password/pwm/config/profile/ChallengeProfile.java

@@ -29,6 +29,7 @@ import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.exception.ChaiValidationException;
 import password.pwm.PwmConstants;
 import password.pwm.config.*;
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.ChallengeValue;
 import password.pwm.cr.ChallengeItemBean;
 import password.pwm.error.ErrorInformation;

+ 2 - 4
pwm/servlet/src/password/pwm/config/profile/ForgottenPasswordProfile.java

@@ -22,11 +22,9 @@
 
 package password.pwm.config.profile;
 
-import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
-import password.pwm.config.StoredConfiguration;
-import password.pwm.config.StoredValue;
+import password.pwm.config.*;
 import password.pwm.config.option.RecoveryVerificationMethods;
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.VerificationMethodValue;
 
 import java.util.*;

+ 1 - 1
pwm/servlet/src/password/pwm/config/profile/HelpdeskProfile.java

@@ -23,7 +23,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.config.PwmSetting;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.StoredValue;
 
 import java.util.Locale;

+ 6 - 2
pwm/servlet/src/password/pwm/config/profile/LdapProfile.java

@@ -24,7 +24,11 @@ package password.pwm.config.profile;
 
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.PwmApplication;
-import password.pwm.config.*;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.StoredValue;
+import password.pwm.config.UserPermission;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.StringUtil;
 
@@ -38,7 +42,7 @@ public class LdapProfile extends AbstractProfile implements Profile {
         super(identifier, storedValueMap);
     }
 
-    public static LdapProfile makeFromStoredConfiguration(final StoredConfiguration storedConfiguration, final String profileID) {
+    public static LdapProfile makeFromStoredConfiguration(final StoredConfigurationImpl storedConfiguration, final String profileID) {
         final Map<PwmSetting,StoredValue> valueMap = new LinkedHashMap<>();
         for (final PwmSetting setting : PwmSettingCategory.LDAP_PROFILE.getSettings()) {
             final StoredValue value = storedConfiguration.readSetting(setting, profileID);

+ 1 - 1
pwm/servlet/src/password/pwm/config/profile/NewUserProfile.java

@@ -31,8 +31,8 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.StoredConfiguration;
 import password.pwm.config.StoredValue;
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;

+ 261 - 252
pwm/servlet/src/password/pwm/config/ConfigurationReader.java → pwm/servlet/src/password/pwm/config/stored/ConfigurationReader.java

@@ -1,252 +1,261 @@
-/*
- * Password Management Servlets (PWM)
- * http://code.google.com/p/pwm/
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2015 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 password.pwm.AppProperty;
-import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
-import password.pwm.bean.SessionLabel;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmOperationalException;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.event.AuditEvent;
-import password.pwm.event.SystemAuditRecord;
-import password.pwm.util.Helper;
-import password.pwm.util.JsonUtil;
-import password.pwm.util.logging.PwmLogger;
-
-import java.io.*;
-import java.math.BigInteger;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Read the PWM configuration.
- *
- * @author Jason D. Rivard
- */
-public class ConfigurationReader {
-// ------------------------------ FIELDS ------------------------------
-
-    private static final PwmLogger LOGGER = PwmLogger.getLogger(ConfigurationReader.class.getName());
-
-    private final File configFile;
-    private final String configFileChecksum;
-    private Configuration configuration;
-    private StoredConfiguration storedConfiguration;
-    private ErrorInformation configFileError;
-
-    private Date configurationReadTime;
-
-    private PwmApplication.MODE configMode = PwmApplication.MODE.NEW;
-
-    private volatile boolean saveInProgress;
-
-    public ConfigurationReader(final File configFile) throws PwmUnrecoverableException {
-        this.configFile = configFile;
-
-        this.configFileChecksum = readFileChecksum(configFile);
-        try {
-            this.storedConfiguration = readStoredConfig();
-            this.configFileError = null;
-        } catch (PwmUnrecoverableException e) {
-            this.configFileError = e.getErrorInformation();
-            LOGGER.warn("error reading configuration file: " + e.getMessage());
-        }
-
-        if (storedConfiguration == null) {
-            this.storedConfiguration = StoredConfiguration.newStoredConfiguration();
-        }
-
-        LOGGER.debug("configuration mode: " + configMode);
-    }
-
-    public PwmApplication.MODE getConfigMode() {
-        return configMode;
-    }
-
-    public StoredConfiguration getStoredConfiguration() {
-        return storedConfiguration;
-    }
-
-    public Configuration getConfiguration() throws PwmUnrecoverableException {
-        if (configuration == null) {
-            configuration = new Configuration(this.storedConfiguration == null ? StoredConfiguration.newStoredConfiguration() : this.storedConfiguration);
-            storedConfiguration.lock();
-        }
-        return configuration;
-    }
-
-    private StoredConfiguration readStoredConfig() throws PwmUnrecoverableException {
-        LOGGER.debug("loading configuration file: " + configFile);
-
-        configurationReadTime = new Date();
-
-        if (!configFile.exists()) {
-            LOGGER.warn("configuration file '" + configFile.getAbsolutePath() + "' does not exist");
-            return null;
-        }
-
-        final InputStream theFileData;
-        try {
-            theFileData = new FileInputStream(configFile);
-        } catch (Exception e) {
-            final String errorMsg = "unable to read configuration file: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
-            this.configMode = PwmApplication.MODE.ERROR;
-            throw new PwmUnrecoverableException(errorInformation);
-        }
-
-        final StoredConfiguration storedConfiguration;
-        try {
-            storedConfiguration = StoredConfiguration.fromXml(theFileData);
-        } catch (PwmUnrecoverableException e) {
-            final String errorMsg = "unable to parse configuration file: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
-            this.configMode = PwmApplication.MODE.ERROR;
-            throw new PwmUnrecoverableException(errorInformation);
-        }
-
-        final List<String> validationErrorMsgs = storedConfiguration.validateValues();
-        if (validationErrorMsgs != null && !validationErrorMsgs.isEmpty()) {
-            final String errorMsg = "value error in config file, please investigate: " + validationErrorMsgs.get(0);
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
-            this.configMode = PwmApplication.MODE.ERROR;
-            throw new PwmUnrecoverableException(errorInformation);
-        }
-
-        final String configIsEditable = storedConfiguration.readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE);
-        if (PwmConstants.TRIAL_MODE || (configIsEditable != null && configIsEditable.equalsIgnoreCase("true"))) {
-            this.configMode = PwmApplication.MODE.CONFIGURATION;
-        } else {
-            this.configMode = PwmApplication.MODE.RUNNING;
-        }
-
-        return storedConfiguration;
-    }
-
-    public void saveConfiguration(
-            final StoredConfiguration storedConfiguration,
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel
-    )
-            throws IOException, PwmUnrecoverableException, PwmOperationalException
-    {
-        File backupDirectory = null;
-        int backupRotations = 0;
-        if (pwmApplication != null) {
-            final Configuration configuration = new Configuration(storedConfiguration);
-            final String backupDirSetting = configuration.readAppProperty(AppProperty.BACKUP_LOCATION);
-            if (backupDirSetting != null && backupDirSetting.length() > 0) {
-                final File pwmPath = pwmApplication.getApplicationPath();
-                backupDirectory = Helper.figureFilepath(backupDirSetting, pwmPath);
-            }
-            backupRotations = Integer.parseInt(configuration.readAppProperty(AppProperty.BACKUP_CONFIG_COUNT));
-        }
-
-
-        { // increment the config epoch
-            String epochStrValue = storedConfiguration.readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_EPOCH);
-            try {
-                final BigInteger epochValue = epochStrValue == null || epochStrValue.length() < 0 ? BigInteger.ZERO : new BigInteger(epochStrValue);
-                epochStrValue = epochValue.add(BigInteger.ONE).toString();
-            } catch (Exception e) {
-                LOGGER.error(sessionLabel, "error trying to parse previous config epoch property: " + e.getMessage());
-                epochStrValue = "0";
-            }
-            storedConfiguration.writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_EPOCH, epochStrValue);
-        }
-
-        if (backupDirectory != null && !backupDirectory.exists()) {
-            if (!backupDirectory.mkdirs()) {
-                throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unable to create backup directory structure '" + backupDirectory.toString() + "'"));
-            }
-        }
-
-        try {
-            LOGGER.info(sessionLabel, "beginning write to configuration file " + configFile.getAbsoluteFile());
-            saveInProgress = true;
-
-            storedConfiguration.toXml(new FileOutputStream(configFile, false));
-            LOGGER.info("saved configuration " + JsonUtil.serialize(storedConfiguration.toJsonDebugObject()));
-            if (pwmApplication != null) {
-                final String actualChecksum = storedConfiguration.settingChecksum();
-                pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum);
-            }
-
-            if (pwmApplication != null && pwmApplication.getAuditManager() != null) {
-                String modifyMessage = storedConfiguration.changeLogAsDebugString(PwmConstants.DEFAULT_LOCALE, false);
-                if (sessionLabel != null && sessionLabel.getUserIdentity() != null) {
-                    modifyMessage += " by " + sessionLabel.getUserIdentity().toDisplayString();
-                }
-                pwmApplication.getAuditManager().submit(SystemAuditRecord.create(
-                        AuditEvent.MODIFY_CONFIGURATION,
-                        modifyMessage,
-                        pwmApplication.getInstanceID()
-                ));
-            }
-
-            if (backupDirectory != null) {
-                final String configFileName = configFile.getName();
-                final String backupFilePath = backupDirectory.getAbsolutePath() + File.separatorChar + configFileName + "-backup";
-                final File backupFile = new File(backupFilePath);
-                Helper.rotateBackups(backupFile, backupRotations);
-                storedConfiguration.toXml(new FileOutputStream(backupFile, false));
-            }
-        } finally {
-            saveInProgress = false;
-        }
-    }
-
-    public boolean modifiedSinceLoad() {
-        final String currentChecksum = readFileChecksum(configFile);
-        return !currentChecksum.equals(configFileChecksum);
-    }
-
-    private static String readFileChecksum(final File file) {
-        if (!file.exists()) {
-            return "";
-        }
-
-        return String.valueOf(file.lastModified() + String.valueOf(file.length()));
-    }
-
-    public Date getConfigurationReadTime() {
-        return configurationReadTime;
-    }
-
-    public ErrorInformation getConfigFileError() {
-        return configFileError;
-    }
-
-    public File getConfigFile() {
-        return configFile;
-    }
-
-    public boolean isSaveInProgress() {
-        return saveInProgress;
-    }
-}
-
-
+/*
+ * Password Management Servlets (PWM)
+ * http://code.google.com/p/pwm/
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2015 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.stored;
+
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
+import password.pwm.config.Configuration;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.event.AuditEvent;
+import password.pwm.event.SystemAuditRecord;
+import password.pwm.util.Helper;
+import password.pwm.util.JsonUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Read the PWM configuration.
+ *
+ * @author Jason D. Rivard
+ */
+public class ConfigurationReader {
+// ------------------------------ FIELDS ------------------------------
+
+    private static final PwmLogger LOGGER = PwmLogger.getLogger(ConfigurationReader.class.getName());
+
+    private final File configFile;
+    private final String configFileChecksum;
+    private Configuration configuration;
+    private StoredConfigurationImpl storedConfiguration;
+    private ErrorInformation configFileError;
+
+    private Date configurationReadTime;
+
+    private PwmApplication.MODE configMode = PwmApplication.MODE.NEW;
+
+    private volatile boolean saveInProgress;
+
+    public ConfigurationReader(final File configFile) throws PwmUnrecoverableException {
+        this.configFile = configFile;
+
+        this.configFileChecksum = readFileChecksum(configFile);
+        try {
+            this.storedConfiguration = readStoredConfig();
+            this.configFileError = null;
+        } catch (PwmUnrecoverableException e) {
+            this.configFileError = e.getErrorInformation();
+            LOGGER.warn("error reading configuration file: " + e.getMessage());
+        }
+
+        if (storedConfiguration == null) {
+            this.storedConfiguration = StoredConfigurationImpl.newStoredConfiguration();
+        }
+
+        LOGGER.debug("configuration mode: " + configMode);
+    }
+
+    public PwmApplication.MODE getConfigMode() {
+        return configMode;
+    }
+
+    public StoredConfigurationImpl getStoredConfiguration() {
+        return storedConfiguration;
+    }
+
+    public Configuration getConfiguration() throws PwmUnrecoverableException {
+        if (configuration == null) {
+            configuration = new Configuration(this.storedConfiguration == null ? StoredConfigurationImpl.newStoredConfiguration() : this.storedConfiguration);
+            storedConfiguration.lock();
+        }
+        return configuration;
+    }
+
+    private StoredConfigurationImpl readStoredConfig() throws PwmUnrecoverableException {
+        LOGGER.debug("loading configuration file: " + configFile);
+
+        configurationReadTime = new Date();
+
+        if (!configFile.exists()) {
+            LOGGER.warn("configuration file '" + configFile.getAbsolutePath() + "' does not exist");
+            return null;
+        }
+
+        try {
+            //InputStream is = new FileInputStream(configFile);
+            //StoredConfiguration sg = new NGStoredConfiguration().fromXml(is);
+            //System.out.print(sg);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        final InputStream theFileData;
+        try {
+            theFileData = new FileInputStream(configFile);
+        } catch (Exception e) {
+            final String errorMsg = "unable to read configuration file: " + e.getMessage();
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
+            this.configMode = PwmApplication.MODE.ERROR;
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+
+        final StoredConfigurationImpl storedConfiguration;
+        try {
+            storedConfiguration = StoredConfigurationImpl.fromXml(theFileData);
+        } catch (PwmUnrecoverableException e) {
+            final String errorMsg = "unable to parse configuration file: " + e.getMessage();
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
+            this.configMode = PwmApplication.MODE.ERROR;
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+
+        final List<String> validationErrorMsgs = storedConfiguration.validateValues();
+        if (validationErrorMsgs != null && !validationErrorMsgs.isEmpty()) {
+            final String errorMsg = "value error in config file, please investigate: " + validationErrorMsgs.get(0);
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
+            this.configMode = PwmApplication.MODE.ERROR;
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+
+        final String configIsEditable = storedConfiguration.readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE);
+        if (PwmConstants.TRIAL_MODE || (configIsEditable != null && configIsEditable.equalsIgnoreCase("true"))) {
+            this.configMode = PwmApplication.MODE.CONFIGURATION;
+        } else {
+            this.configMode = PwmApplication.MODE.RUNNING;
+        }
+
+        return storedConfiguration;
+    }
+
+    public void saveConfiguration(
+            final StoredConfigurationImpl storedConfiguration,
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel
+    )
+            throws IOException, PwmUnrecoverableException, PwmOperationalException
+    {
+        File backupDirectory = null;
+        int backupRotations = 0;
+        if (pwmApplication != null) {
+            final Configuration configuration = new Configuration(storedConfiguration);
+            final String backupDirSetting = configuration.readAppProperty(AppProperty.BACKUP_LOCATION);
+            if (backupDirSetting != null && backupDirSetting.length() > 0) {
+                final File pwmPath = pwmApplication.getApplicationPath();
+                backupDirectory = Helper.figureFilepath(backupDirSetting, pwmPath);
+            }
+            backupRotations = Integer.parseInt(configuration.readAppProperty(AppProperty.BACKUP_CONFIG_COUNT));
+        }
+
+
+        { // increment the config epoch
+            String epochStrValue = storedConfiguration.readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_EPOCH);
+            try {
+                final BigInteger epochValue = epochStrValue == null || epochStrValue.length() < 0 ? BigInteger.ZERO : new BigInteger(epochStrValue);
+                epochStrValue = epochValue.add(BigInteger.ONE).toString();
+            } catch (Exception e) {
+                LOGGER.error(sessionLabel, "error trying to parse previous config epoch property: " + e.getMessage());
+                epochStrValue = "0";
+            }
+            storedConfiguration.writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_EPOCH, epochStrValue);
+        }
+
+        if (backupDirectory != null && !backupDirectory.exists()) {
+            if (!backupDirectory.mkdirs()) {
+                throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unable to create backup directory structure '" + backupDirectory.toString() + "'"));
+            }
+        }
+
+        try {
+            LOGGER.info(sessionLabel, "beginning write to configuration file " + configFile.getAbsoluteFile());
+            saveInProgress = true;
+
+            storedConfiguration.toXml(new FileOutputStream(configFile, false));
+            LOGGER.info("saved configuration " + JsonUtil.serialize(storedConfiguration.toJsonDebugObject()));
+            if (pwmApplication != null) {
+                final String actualChecksum = storedConfiguration.settingChecksum();
+                pwmApplication.writeAppAttribute(PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum);
+            }
+
+            if (pwmApplication != null && pwmApplication.getAuditManager() != null) {
+                String modifyMessage = storedConfiguration.changeLogAsDebugString(PwmConstants.DEFAULT_LOCALE, false);
+                if (sessionLabel != null && sessionLabel.getUserIdentity() != null) {
+                    modifyMessage += " by " + sessionLabel.getUserIdentity().toDisplayString();
+                }
+                pwmApplication.getAuditManager().submit(SystemAuditRecord.create(
+                        AuditEvent.MODIFY_CONFIGURATION,
+                        modifyMessage,
+                        pwmApplication.getInstanceID()
+                ));
+            }
+
+            if (backupDirectory != null) {
+                final String configFileName = configFile.getName();
+                final String backupFilePath = backupDirectory.getAbsolutePath() + File.separatorChar + configFileName + "-backup";
+                final File backupFile = new File(backupFilePath);
+                Helper.rotateBackups(backupFile, backupRotations);
+                storedConfiguration.toXml(new FileOutputStream(backupFile, false));
+            }
+        } finally {
+            saveInProgress = false;
+        }
+    }
+
+    public boolean modifiedSinceLoad() {
+        final String currentChecksum = readFileChecksum(configFile);
+        return !currentChecksum.equals(configFileChecksum);
+    }
+
+    private static String readFileChecksum(final File file) {
+        if (!file.exists()) {
+            return "";
+        }
+
+        return String.valueOf(file.lastModified() + String.valueOf(file.length()));
+    }
+
+    public Date getConfigurationReadTime() {
+        return configurationReadTime;
+    }
+
+    public ErrorInformation getConfigFileError() {
+        return configFileError;
+    }
+
+    public File getConfigFile() {
+        return configFile;
+    }
+
+    public boolean isSaveInProgress() {
+        return saveInProgress;
+    }
+}
+
+

+ 158 - 0
pwm/servlet/src/password/pwm/config/stored/NGStoredConfiguration.java

@@ -0,0 +1,158 @@
+package password.pwm.config.stored;
+
+import org.jdom2.Document;
+import org.jdom2.Element;
+import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.StoredValue;
+import password.pwm.config.value.ValueFactory;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.XmlUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+class NGStoredConfiguration implements StoredConfiguration,  StoredConfigurationProvider {
+    final static private PwmLogger LOGGER = PwmLogger.forClass(NGStoredConfiguration.class);
+
+    public static final String XML_ELEMENT_ROOT = "PwmConfiguration";
+    public static final String XML_ELEMENT_PROPERTIES = "properties";
+    public static final String XML_ELEMENT_PROPERTY = "property";
+    public static final String XML_ELEMENT_SETTINGS = "settings";
+    public static final String XML_ELEMENT_SETTING = "setting";
+    public static final String XML_ELEMENT_DEFAULT = "default";
+
+    public static final String XML_ATTRIBUTE_TYPE = "type";
+    public static final String XML_ATTRIBUTE_KEY = "key";
+    public static final String XML_ATTRIBUTE_SYNTAX = "syntax";
+    public static final String XML_ATTRIBUTE_PROFILE = "profile";
+    public static final String XML_ATTRIBUTE_VALUE_APP = "app";
+    public static final String XML_ATTRIBUTE_VALUE_CONFIG = "config";
+    public static final String XML_ATTRIBUTE_CREATE_TIME = "createTime";
+    public static final String XML_ATTRIBUTE_MODIFY_TIME = "modifyTime";
+    public static final String XML_ATTRIBUTE_MODIFY_USER = "modifyUser";
+    public static final String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion";
+
+    final Map<StoredConfigReference, ValueWrapper> values = new HashMap<>();
+    static class ValueWrapper {
+        private final Date modifyDate;
+        private final UserIdentity modifyUser;
+        private final StoredValue storedValue;
+
+        public ValueWrapper(Date modifyDate, UserIdentity modifyUser, StoredValue storedValue) {
+            this.modifyDate = modifyDate;
+            this.modifyUser = modifyUser;
+            this.storedValue = storedValue;
+        }
+
+        public Date getModifyDate() {
+            return modifyDate;
+        }
+
+        public UserIdentity getModifyUser() {
+            return modifyUser;
+        }
+
+        public StoredValue getStoredValue() {
+            return storedValue;
+        }
+    }
+
+    @Override
+    public StoredConfiguration fromXml(InputStream inputStream) throws PwmUnrecoverableException {
+        return fromXmlImpl(inputStream);
+    }
+
+
+    public static StoredConfiguration fromXmlImpl(InputStream inputStream) throws PwmUnrecoverableException {
+        final NGStoredConfiguration config = new NGStoredConfiguration();
+        final Document inputDocument = XmlUtil.parseXml(inputStream);
+        final Element rootElement = inputDocument.getRootElement();
+        final Element settingsElement = rootElement.getChild("settings");
+        for (final Element settingElement : settingsElement.getChildren("setting")) {
+            try {
+                final String key = settingElement.getAttributeValue("key");
+                final PwmSetting pwmSetting = PwmSetting.forKey(key);
+
+                if (pwmSetting == null) {
+                    LOGGER.debug("ignoring setting for unknown key: " + key);
+                } else {
+                    final String profileID = settingElement.getAttributeValue(XML_ATTRIBUTE_PROFILE);
+                    LOGGER.trace("parsing setting key=" + key + ", profile=" + profileID);
+                    final String modifyDateStr = settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME);
+                    final Date modifyDate = modifyDateStr == null || modifyDateStr.isEmpty()
+                            ? null
+                            : PwmConstants.DEFAULT_DATETIME_FORMAT.parse(modifyDateStr);
+
+
+                    StoredValue storedValue = null;
+                    if (settingElement.getChild(XML_ELEMENT_DEFAULT) != null) {
+                        if (!pwmSetting.isConfidential()) { //@todo temporary
+                            storedValue = ValueFactory.fromXmlValues(pwmSetting, settingElement, null);
+                        }
+                    }
+                    config.values.put(
+                            new StoredConfigReferenceBean(StoredConfigReference.Type.Setting, key, profileID),
+                            new ValueWrapper(modifyDate, null, storedValue)
+                    );
+                }
+            } catch (PwmOperationalException | ParseException e) {
+                e.printStackTrace();
+            }
+        }
+        return config;
+    }
+
+    @Override
+    public void toXml(OutputStream outputStream) {
+
+    }
+
+    @Override
+    public void resetSetting(PwmSetting setting, String profileID, UserIdentity userIdentity) {
+
+    }
+
+    @Override
+    public boolean isDefaultValue(PwmSetting setting) {
+        return false;
+    }
+
+    @Override
+    public boolean isDefaultValue(PwmSetting setting, String profileID) {
+        return false;
+    }
+
+    @Override
+    public StoredValue readSetting(PwmSetting setting) {
+        return null;
+    }
+
+    @Override
+    public StoredValue readSetting(PwmSetting setting, String profileID) {
+        return null;
+    }
+
+    @Override
+    public void copyProfileID(PwmSettingCategory category, String sourceID, String destinationID, UserIdentity userIdentity) throws PwmUnrecoverableException {
+
+    }
+
+    @Override
+    public void writeSetting(PwmSetting setting, StoredValue value, UserIdentity userIdentity) throws PwmUnrecoverableException {
+
+    }
+
+    @Override
+    public void writeSetting(PwmSetting setting, String profileID, StoredValue value, UserIdentity userIdentity) throws PwmUnrecoverableException {
+
+    }
+}

+ 11 - 0
pwm/servlet/src/password/pwm/config/stored/StoredConfigReference.java

@@ -0,0 +1,11 @@
+package password.pwm.config.stored;
+
+public interface StoredConfigReference {
+    Type getType();
+    String getKey();
+    String getProfileID();
+
+    enum Type {
+        Setting;
+    }
+}

+ 59 - 0
pwm/servlet/src/password/pwm/config/stored/StoredConfigReferenceBean.java

@@ -0,0 +1,59 @@
+package password.pwm.config.stored;
+
+import java.io.Serializable;
+
+class StoredConfigReferenceBean implements StoredConfigReference, Serializable {
+    private final StoredConfigReference.Type type;
+    private final String key;
+    private final String profileID;
+
+    StoredConfigReferenceBean(Type type, String key, String profileID) {
+        if (type == null) {
+            throw new NullPointerException("type can not be null");
+        }
+
+        if (key == null) {
+            throw new NullPointerException("key can not be null");
+        }
+
+        this.type = type;
+        this.key = key;
+        this.profileID = profileID;
+    }
+
+    @Override
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public String getProfileID() {
+        return profileID;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        StoredConfigReferenceBean that = (StoredConfigReferenceBean) o;
+
+        if (type != that.type) return false;
+        if (!key.equals(that.key)) return false;
+        return !(profileID != null ? !profileID.equals(that.profileID) : that.profileID != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = type.hashCode();
+        result = 31 * result + key.hashCode();
+        result = 31 * result + (profileID != null ? profileID.hashCode() : 0);
+        return result;
+    }
+}

+ 35 - 0
pwm/servlet/src/password/pwm/config/stored/StoredConfiguration.java

@@ -0,0 +1,35 @@
+package password.pwm.config.stored;
+
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.StoredValue;
+import password.pwm.error.PwmUnrecoverableException;
+
+public interface StoredConfiguration {
+    void resetSetting(PwmSetting setting, String profileID, UserIdentity userIdentity);
+
+    boolean isDefaultValue(PwmSetting setting);
+
+    boolean isDefaultValue(PwmSetting setting, String profileID);
+
+    StoredValue readSetting(PwmSetting setting);
+
+    StoredValue readSetting(PwmSetting setting, String profileID);
+
+    void copyProfileID(PwmSettingCategory category, String sourceID, String destinationID, UserIdentity userIdentity)
+            throws PwmUnrecoverableException;
+
+    void writeSetting(
+            PwmSetting setting,
+            StoredValue value,
+            UserIdentity userIdentity
+    ) throws PwmUnrecoverableException;
+
+    void writeSetting(
+            PwmSetting setting,
+            String profileID,
+            StoredValue value,
+            UserIdentity userIdentity
+    ) throws PwmUnrecoverableException;
+}

+ 1562 - 1521
pwm/servlet/src/password/pwm/config/StoredConfiguration.java → pwm/servlet/src/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -1,1521 +1,1562 @@
-/*
- * Password Management Servlets (PWM)
- * http://code.google.com/p/pwm/
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2015 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 org.jdom2.*;
-import org.jdom2.xpath.XPathExpression;
-import org.jdom2.xpath.XPathFactory;
-import password.pwm.AppProperty;
-import password.pwm.PwmConstants;
-import password.pwm.bean.UserIdentity;
-import password.pwm.config.option.ADPolicyComplexity;
-import password.pwm.config.value.PasswordValue;
-import password.pwm.config.value.StringArrayValue;
-import password.pwm.config.value.StringValue;
-import password.pwm.config.value.ValueFactory;
-import password.pwm.error.*;
-import password.pwm.i18n.Config;
-import password.pwm.i18n.LocaleHelper;
-import password.pwm.i18n.PwmLocaleBundle;
-import password.pwm.util.*;
-import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmRandom;
-import password.pwm.util.secure.PwmSecurityKey;
-import password.pwm.util.secure.SecureEngine;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Serializable;
-import java.text.ParseException;
-import java.util.*;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-/**
- * @author Jason D. Rivard
- */
-public class StoredConfiguration implements Serializable {
-// ------------------------------ FIELDS ------------------------------
-
-    public enum ConfigProperty {
-        PROPERTY_KEY_CONFIG_IS_EDITABLE("configIsEditable"),
-        PROPERTY_KEY_CONFIG_EPOCH("configEpoch"),
-        PROPERTY_KEY_TEMPLATE("configTemplate"),
-        PROPERTY_KEY_NOTES("notes"),
-        PROPERTY_KEY_PASSWORD_HASH("configPasswordHash"),
-        PROPERTY_KEY_SAVE_CONFIG_ON_START("saveConfigOnStart"),
-        ;
-
-        private final String key;
-
-        private ConfigProperty(String key)
-        {
-            this.key = key;
-        }
-
-        public String getKey()
-        {
-            return key;
-        }
-    }
-
-    public static class SettingMetaData implements Serializable {
-        private Date modifyDate;
-        private UserIdentity userIdentity;
-
-        public Date getModifyDate()
-        {
-            return modifyDate;
-        }
-
-        public UserIdentity getUserIdentity()
-        {
-            return userIdentity;
-        }
-    }
-
-    private static final PwmLogger LOGGER = PwmLogger.forClass(StoredConfiguration.class);
-    private static final String XML_FORMAT_VERSION = "4";
-
-    public static final String XML_ELEMENT_ROOT = "PwmConfiguration";
-    public static final String XML_ELEMENT_PROPERTIES = "properties";
-    public static final String XML_ELEMENT_PROPERTY = "property";
-    public static final String XML_ELEMENT_SETTINGS = "settings";
-    public static final String XML_ELEMENT_SETTING = "setting";
-    public static final String XML_ELEMENT_DEFAULT = "default";
-
-    public static final String XML_ATTRIBUTE_TYPE = "type";
-    public static final String XML_ATTRIBUTE_KEY = "key";
-    public static final String XML_ATTRIBUTE_SYNTAX = "syntax";
-    public static final String XML_ATTRIBUTE_PROFILE = "profile";
-    public static final String XML_ATTRIBUTE_VALUE_APP = "app";
-    public static final String XML_ATTRIBUTE_VALUE_CONFIG = "config";
-    public static final String XML_ATTRIBUTE_CREATE_TIME = "createTime";
-    public static final String XML_ATTRIBUTE_MODIFY_TIME = "modifyTime";
-    public static final String XML_ATTRIBUTE_MODIFY_USER = "modifyUser";
-    public static final String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion";
-
-    private Document document = new Document(new Element(XML_ELEMENT_ROOT));
-    private ChangeLog changeLog = new ChangeLog();
-
-    private boolean locked = false;
-    private boolean setting_writeLabels = true;
-    private final ReentrantReadWriteLock domModifyLock = new ReentrantReadWriteLock();
-
-// -------------------------- STATIC METHODS --------------------------
-
-    public static StoredConfiguration newStoredConfiguration() throws PwmUnrecoverableException {
-        return new StoredConfiguration();
-    }
-
-    public static StoredConfiguration copy(final StoredConfiguration input) throws PwmUnrecoverableException {
-        final StoredConfiguration copy = new StoredConfiguration();
-        copy.document = input.document.clone();
-        return copy;
-    }
-
-    public static StoredConfiguration fromXml(final InputStream xmlData)
-            throws PwmUnrecoverableException
-    {
-        final Date startTime = new Date();
-        //validateXmlSchema(xmlData);
-
-        final Document inputDocument = XmlUtil.parseXml(xmlData);
-        final StoredConfiguration newConfiguration = StoredConfiguration.newStoredConfiguration();
-
-        try {
-            newConfiguration.document = inputDocument;
-            newConfiguration.createTime(); // verify create time;
-            ConfigurationCleaner.cleanup(newConfiguration);
-        } catch (Exception e) {
-            final String errorMsg = "error reading configuration file format, error=" + e.getMessage();
-            final ErrorInformation errorInfo = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
-            throw new PwmUnrecoverableException(errorInfo);
-        }
-
-        checkIfXmlRequiresUpdate(newConfiguration);
-        final TimeDuration totalDuration = TimeDuration.fromCurrent(startTime);
-        LOGGER.debug("successfully loaded configuration (" + totalDuration.asCompactString() + ")");
-        return newConfiguration;
-    }
-
-    /**
-     * Loop through all settings to see if setting value has flag {@link StoredValue#requiresStoredUpdate()} set to true.
-     * If so, then call {@link #writeSetting(PwmSetting, StoredValue, password.pwm.bean.UserIdentity)} or {@link #writeSetting(PwmSetting, String, StoredValue, password.pwm.bean.UserIdentity)}
-     * for that value so that the xml dom can be updated.
-     * @param storedConfiguration stored configuration to check
-     */
-    private static void checkIfXmlRequiresUpdate(final StoredConfiguration storedConfiguration) throws PwmUnrecoverableException {
-        for (final PwmSetting setting : PwmSetting.values()) {
-            if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
-                final StoredValue value = storedConfiguration.readSetting(setting);
-                if (value.requiresStoredUpdate()) {
-                    storedConfiguration.writeSetting(setting, value, null);
-                }
-            }
-        }
-
-        for (final PwmSettingCategory category : PwmSettingCategory.values()) {
-            if (category.hasProfiles()) {
-                for (final String profileID : storedConfiguration.profilesForSetting(category.getProfileSetting())) {
-                    for (final PwmSetting profileSetting : category.getSettings()) {
-                        final StoredValue value = storedConfiguration.readSetting(profileSetting, profileID);
-                        if (value.requiresStoredUpdate()) {
-                            storedConfiguration.writeSetting(profileSetting, profileID, value, null);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    public void resetAllPasswordValues(final String comment) {
-        for (final Iterator<SettingValueRecord> settingValueRecordIterator = new StoredValueIterator(false); settingValueRecordIterator.hasNext();) {
-            final SettingValueRecord settingValueRecord = settingValueRecordIterator.next();
-            if (settingValueRecord.getSetting().getSyntax() == PwmSettingSyntax.PASSWORD) {
-                this.resetSetting(settingValueRecord.getSetting(),settingValueRecord.getProfile(),null);
-                if (comment != null && !comment.isEmpty()) {
-                    final XPathExpression xp = XPathBuilder.xpathForSetting(settingValueRecord.getSetting(), settingValueRecord.getProfile());
-                    final Element settingElement = (Element)xp.evaluateFirst(document);
-                    if (settingElement != null) {
-                        settingElement.addContent(new Comment(comment));
-                    }
-                }
-            }
-        }
-    }
-
-    public StoredConfiguration() throws PwmUnrecoverableException {
-        ConfigurationCleaner.cleanup(this);
-        final String createTime = PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date());
-        document.getRootElement().setAttribute(XML_ATTRIBUTE_CREATE_TIME,createTime);
-    }
-
-
-    public String readConfigProperty(final ConfigProperty propertyName) {
-        final XPathExpression xp = XPathBuilder.xpathForConfigProperty(propertyName);
-        final Element propertyElement = (Element)xp.evaluateFirst(document);
-        return propertyElement == null ? null : propertyElement.getText();
-    }
-
-    public void writeConfigProperty(
-            final ConfigProperty propertyName,
-            final String value
-    ) {
-        domModifyLock.writeLock().lock();
-        try {
-
-            final XPathExpression xp = XPathBuilder.xpathForConfigProperty(propertyName);
-            final List<Element> propertyElements = xp.evaluate(document);
-            for (final Element propertyElement : propertyElements) {
-                propertyElement.detach();
-            }
-
-            final Element propertyElement = new Element(XML_ELEMENT_PROPERTY);
-            propertyElement.setAttribute(new Attribute(XML_ATTRIBUTE_KEY,propertyName.getKey()));
-            propertyElement.setContent(new Text(value));
-
-            if (null == XPathBuilder.xpathForConfigProperties().evaluateFirst(document)) {
-                Element configProperties = new Element(XML_ELEMENT_PROPERTIES);
-                configProperties.setAttribute(new Attribute(XML_ATTRIBUTE_TYPE,XML_ATTRIBUTE_VALUE_CONFIG));
-                document.getRootElement().addContent(configProperties);
-            }
-
-            final XPathExpression xp2 = XPathBuilder.xpathForConfigProperties();
-            final Element propertiesElement = (Element)xp2.evaluateFirst(document);
-            propertyElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-            propertiesElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-            propertiesElement.addContent(propertyElement);
-        } finally {
-            domModifyLock.writeLock().unlock();
-        }
-    }
-
-    public void lock() {
-        locked = true;
-    }
-
-    public Map<String,String> readLocaleBundleMap(final String bundleName, final String keyName) {
-        domModifyLock.readLock().lock();
-        try {
-            final XPathExpression xp = XPathBuilder.xpathForLocaleBundleSetting(bundleName, keyName);
-            final Element localeBundleElement = (Element)xp.evaluateFirst(document);
-            if (localeBundleElement != null) {
-                final Map<String,String> bundleMap = new LinkedHashMap<>();
-                for (final Element valueElement : localeBundleElement.getChildren("value")) {
-                    final String localeStrValue = valueElement.getAttributeValue("locale");
-                    bundleMap.put(localeStrValue == null ? "" : localeStrValue, valueElement.getText());
-                }
-                if (!bundleMap.isEmpty()) {
-                    return bundleMap;
-                }
-            }
-        } finally {
-            domModifyLock.readLock().unlock();
-        }
-        return Collections.emptyMap();
-    }
-
-    public Map<String,Object> toOutputMap(final Locale locale) {
-        final List<Map<String,String>> settingData = new ArrayList<>();
-        for (final StoredConfiguration.SettingValueRecord settingValueRecord : this.modifiedSettings()) {
-            final Map<String,String> recordMap = new HashMap<>();
-            recordMap.put("label", settingValueRecord.getSetting().getLabel(locale));
-            if (settingValueRecord.getProfile() != null) {
-                recordMap.put("profile", settingValueRecord.getProfile());
-            }
-            if (settingValueRecord.getStoredValue() != null) {
-                recordMap.put("value", settingValueRecord.getStoredValue().toDebugString(locale));
-            }
-            final SettingMetaData settingMetaData = readSettingMetadata(settingValueRecord.getSetting(), settingValueRecord.getProfile());
-            if (settingMetaData != null) {
-                if (settingMetaData.getModifyDate() != null) {
-                    recordMap.put("modifyTime", PwmConstants.DEFAULT_DATETIME_FORMAT.format(settingMetaData.getModifyDate()));
-                }
-                if (settingMetaData.getUserIdentity() != null) {
-                    recordMap.put("modifyUser",settingMetaData.getUserIdentity().toDisplayString());
-                }
-            }
-            settingData.add(recordMap);
-        }
-
-        final HashMap<String,Object> outputObj = new HashMap<>();
-        outputObj.put("settings",settingData);
-        outputObj.put("template", this.getTemplate().toString());
-
-        return Collections.unmodifiableMap(outputObj);
-    }
-
-    public void resetLocaleBundleMap(final String bundleName, final String keyName) {
-        preModifyActions();
-        domModifyLock.writeLock().lock();
-        try {
-            final XPathExpression xp = XPathBuilder.xpathForLocaleBundleSetting(bundleName, keyName);
-            final List<Element> oldBundleElements = xp.evaluate(document);
-            if (oldBundleElements != null) {
-                for (final Element element : oldBundleElements) {
-                    element.detach();
-                }
-            }
-        } finally {
-            domModifyLock.writeLock().unlock();
-        }
-    }
-
-    public void resetSetting(final PwmSetting setting, final UserIdentity userIdentity) {
-        resetSetting(setting, null, userIdentity);
-    }
-
-    public void resetSetting(final PwmSetting setting, final String profileID, final UserIdentity userIdentity) {
-        changeLog.updateChangeLog(setting, profileID, defaultValue(setting, this.getTemplate()));
-        domModifyLock.writeLock().lock();
-        preModifyActions();
-        try {
-            final Element settingElement = createOrGetSettingElement(document, setting, profileID);
-            settingElement.removeContent();
-            settingElement.addContent(new Element(XML_ELEMENT_DEFAULT));
-            updateMetaData(settingElement, userIdentity);
-        } finally {
-            domModifyLock.writeLock().unlock();
-        }
-    }
-
-    public boolean isDefaultValue(final PwmSetting setting) {
-        return isDefaultValue(setting, null);
-    }
-
-    public boolean isDefaultValue(final PwmSetting setting, final String profileID) {
-        domModifyLock.readLock().lock();
-        try {
-            final StoredValue currentValue = readSetting(setting, profileID);
-            if (setting.getSyntax() == PwmSettingSyntax.PASSWORD) {
-                return currentValue == null || currentValue.toNativeObject() == null;
-            }
-            final StoredValue defaultValue = defaultValue(setting, this.getTemplate());
-            final String currentJsonValue = JsonUtil.serialize((Serializable)currentValue.toNativeObject());
-            final String defaultJsonValue = JsonUtil.serialize((Serializable)defaultValue.toNativeObject());
-            return defaultJsonValue.equalsIgnoreCase(currentJsonValue);
-        } finally {
-            domModifyLock.readLock().unlock();
-        }
-    }
-
-    private static StoredValue defaultValue(final PwmSetting pwmSetting, final PwmSettingTemplate template)
-    {
-        try {
-            return pwmSetting.getDefaultValue(template);
-        } catch (PwmException e) {
-            final String errorMsg = "error reading default value for setting " + pwmSetting.toString() + ", error: " + e.getErrorInformation().toDebugStr();
-            LOGGER.error(errorMsg,e);
-            throw new IllegalStateException(errorMsg);
-        }
-    }
-
-    public PwmSettingTemplate getTemplate() {
-        final String propertyValue = readConfigProperty(ConfigProperty.PROPERTY_KEY_TEMPLATE);
-        try {
-            return PwmSettingTemplate.valueOf(propertyValue);
-        } catch (IllegalArgumentException e) {
-            return PwmSettingTemplate.DEFAULT;
-        } catch (NullPointerException e) {
-            return PwmSettingTemplate.DEFAULT;
-        }
-    }
-
-    public void setTemplate(PwmSettingTemplate template) {
-        writeConfigProperty(ConfigProperty.PROPERTY_KEY_TEMPLATE, template.toString());
-    }
-
-    public String toString(final PwmSetting setting, final String profileID ) {
-        final StoredValue storedValue = readSetting(setting, profileID);
-        return setting.getKey() + "=" + storedValue.toDebugString(null);
-    }
-
-    public Map<String,String> getModifiedSettingDebugValues(final Locale locale, final boolean prettyPrint) {
-        final Map<String,String> returnObj = new LinkedHashMap<>();
-        for (SettingValueRecord record : this.modifiedSettings()) {
-            final String label = record.getSetting().toMenuLocationDebug(record.getProfile(),locale);
-            final String value = record.getStoredValue().toDebugString(locale);
-            returnObj.put(label,value);
-        }
-        return returnObj;
-    }
-
-    List<SettingValueRecord> modifiedSettings() {
-        final List<SettingValueRecord> returnObj = new ArrayList<>();
-        domModifyLock.readLock().lock();
-        try {
-            for (final PwmSetting setting : PwmSetting.values()) {
-                if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
-                    if (!isDefaultValue(setting,null)) {
-                        final StoredValue value = readSetting(setting);
-                        if (value != null) {
-                            returnObj.add(new SettingValueRecord(setting, null, value));
-                        }
-                    }
-                }
-            }
-
-            for (final PwmSettingCategory category : PwmSettingCategory.values()) {
-                if (category.hasProfiles()) {
-                    for (final String profileID : this.profilesForSetting(category.getProfileSetting())) {
-                        for (final PwmSetting profileSetting : category.getSettings()) {
-                            if (!isDefaultValue(profileSetting, profileID)) {
-                                final StoredValue value = readSetting(profileSetting, profileID);
-                                if (value != null) {
-                                    returnObj.add(new SettingValueRecord(profileSetting, profileID, value));
-
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            return returnObj;
-        } finally {
-            domModifyLock.readLock().unlock();
-        }
-    }
-
-    public Serializable toJsonDebugObject() {
-        domModifyLock.readLock().lock();
-        try {
-            final TreeMap<String,Object> outputObject = new TreeMap<>();
-
-            for (final PwmSetting setting : PwmSetting.values()) {
-                if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
-                    if (!isDefaultValue(setting,null)) {
-                        final StoredValue value = readSetting(setting);
-                        outputObject.put(setting.getKey(), value.toDebugJsonObject(null));
-                    }
-                }
-            }
-
-            for (final PwmSettingCategory category : PwmSettingCategory.values()) {
-                if (category.hasProfiles()) {
-                    final TreeMap<String,Object> profiles = new TreeMap<>();
-                    for (final String profileID : this.profilesForSetting(category.getProfileSetting())) {
-                        final TreeMap<String,Object> profileObject = new TreeMap<>();
-                        for (final PwmSetting profileSetting : category.getSettings()) {
-                            if (!isDefaultValue(profileSetting, profileID)) {
-                                final StoredValue value = readSetting(profileSetting, profileID);
-                                profileObject.put(profileSetting.getKey(), value.toDebugJsonObject(null));
-                            }
-                        }
-                        profiles.put(profileID,profileObject);
-                    }
-                    outputObject.put(category.getProfileSetting().getKey(),profiles);
-                }
-            }
-
-            return outputObject;
-        } finally {
-            domModifyLock.readLock().unlock();
-        }
-    }
-
-    public void toXml(final OutputStream outputStream)
-            throws IOException, PwmUnrecoverableException
-    {
-        ConfigurationCleaner.updateMandatoryElements(document);
-        XmlUtil.outputDocument(document, outputStream);
-    }
-
-    public List<String> profilesForSetting(final PwmSetting pwmSetting) {
-        if (!pwmSetting.getCategory().hasProfiles() && pwmSetting.getSyntax() != PwmSettingSyntax.PROFILE) {
-            throw new IllegalArgumentException("cannot build profile list for non-profile setting " + pwmSetting.toString());
-        }
-
-        final PwmSetting profileSetting;
-        if (pwmSetting.getSyntax() == PwmSettingSyntax.PROFILE) {
-            profileSetting = pwmSetting;
-        } else {
-            profileSetting = pwmSetting.getCategory().getProfileSetting();
-        }
-
-        final List<String> settingValues = (List<String>)readSetting(profileSetting).toNativeObject();
-        final LinkedList<String> profiles = new LinkedList<>();
-        profiles.addAll(settingValues);
-        for (Iterator<String> iterator = profiles.iterator(); iterator.hasNext();) {
-            final String profile = iterator.next();
-            if (profile == null || profile.length() < 1) {
-                iterator.remove();
-            }
-        }
-        return Collections.unmodifiableList(profiles);
-    }
-
-    public List<String> validateValues() {
-        final long startTime = System.currentTimeMillis();
-        final List<String> errorStrings = new ArrayList<>();
-
-        for (final PwmSetting loopSetting : PwmSetting.values()) {
-
-            if (loopSetting.getCategory().hasProfiles()) {
-                for (final String profile : profilesForSetting(loopSetting)) {
-                    final String errorAppend = profile;
-                    final StoredValue loopValue = readSetting(loopSetting,profile);
-
-                    try {
-                        final List<String> errors = loopValue.validateValue(loopSetting);
-                        for (final String loopError : errors) {
-                            errorStrings.add(loopSetting.toMenuLocationDebug(profile,PwmConstants.DEFAULT_LOCALE) + errorAppend + " " + loopError);
-                        }
-                    } catch (Exception e) {
-                        LOGGER.error("unexpected error during validate value for " + loopSetting.toMenuLocationDebug(profile,PwmConstants.DEFAULT_LOCALE) + errorAppend + ", error: " + e.getMessage(),e);
-                    }
-                }
-            } else {
-                final StoredValue loopValue = readSetting(loopSetting);
-
-                try {
-                    final List<String> errors = loopValue.validateValue(loopSetting);
-                    for (final String loopError : errors) {
-                        errorStrings.add(loopSetting.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE) + loopError);
-                    }
-                } catch (Exception e) {
-                    LOGGER.error("unexpected error during validate value for " + loopSetting.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE) + ", error: " + e.getMessage(),e);
-                }
-            }
-        }
-
-        LOGGER.trace("StoredConfiguration validator completed in " + TimeDuration.fromCurrent(startTime).asCompactString());
-        return errorStrings;
-    }
-
-    public SettingMetaData readSettingMetadata(final PwmSetting setting, final String profileID) {
-        final XPathExpression xp = XPathBuilder.xpathForSetting(setting, profileID);
-        final Element settingElement = (Element)xp.evaluateFirst(document);
-
-        if (settingElement == null) {
-            return null;
-        }
-
-        final SettingMetaData metaData = new SettingMetaData();
-        try {
-            if (settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME) != null) {
-                metaData.modifyDate = PwmConstants.DEFAULT_DATETIME_FORMAT.parse(
-                        settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME));
-            }
-        } catch (Exception e) {
-            LOGGER.error("can't read modifyDate for setting " + setting.getKey() + ", profile " + profileID + ", error: " + e.getMessage());
-        }
-        try {
-            if (settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_USER) != null) {
-                metaData.userIdentity = UserIdentity.fromDelimitedKey(
-                        settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_USER));
-            }
-        } catch (Exception e) {
-            LOGGER.error("can't read userIdentity for setting " + setting.getKey() + ", profile " + profileID + ", error: " + e.getMessage());
-        }
-        return metaData;
-    }
-
-    public List<ConfigRecordID> search(final String searchTerm, final Locale locale) {
-        if (searchTerm == null) {
-            return Collections.emptyList();
-        }
-
-        final LinkedHashSet<ConfigRecordID> returnSet = new LinkedHashSet<>();
-        boolean firstIter = true;
-        for (final String searchWord : StringUtil.whitespaceSplit(searchTerm)) { // split on whitespace
-            final LinkedHashSet<ConfigRecordID> loopResults = new LinkedHashSet<>();
-            for (final PwmSetting loopSetting : PwmSetting.values()) {
-                if (loopSetting.getCategory().hasProfiles()) {
-                    for (final String profile : profilesForSetting(loopSetting)) {
-                        final StoredValue loopValue = readSetting(loopSetting, profile);
-                        if (matchSetting(loopSetting,loopValue,searchWord,locale)) {
-                            loopResults.add(new ConfigRecordID(ConfigRecordID.RecordType.SETTING,loopSetting,profile));
-                        }
-                    }
-                } else {
-                    final StoredValue loopValue = readSetting(loopSetting);
-                    if (matchSetting(loopSetting,loopValue,searchWord,locale)) {
-                        loopResults.add(new ConfigRecordID(ConfigRecordID.RecordType.SETTING,loopSetting,null));
-                    }
-                }
-            }
-            if (firstIter) {
-                returnSet.addAll(loopResults);
-            } else {
-                returnSet.retainAll(loopResults);
-            }
-            firstIter = false;
-        }
-
-        return new ArrayList<>(returnSet);
-    }
-
-    public boolean matchSetting(final PwmSetting setting, final StoredValue value, final String searchTerm, final Locale locale) {
-        if (setting.isHidden() || setting.getCategory().isHidden()) {
-            return false;
-        }
-
-        if (searchTerm == null || searchTerm.isEmpty()) {
-            return false;
-        }
-
-        final String lowerSearchTerm = searchTerm.toLowerCase();
-
-        {
-            final String key = setting.getKey();
-            if (key.toLowerCase().contains(lowerSearchTerm)) {
-                return true;
-            }
-        }
-        {
-            final String label = setting.getLabel(locale);
-            if (label.toLowerCase().contains(lowerSearchTerm)) {
-                return true;
-            }
-        }
-        {
-            final String descr = setting.getDescription(locale);
-            if (descr.toLowerCase().contains(lowerSearchTerm)) {
-                return true;
-            }
-        }
-        {
-            final String menuLocationString = setting.toMenuLocationDebug(null,locale);
-            if (menuLocationString.toLowerCase().contains(lowerSearchTerm)) {
-                return true;
-            }
-        }
-
-        if (setting.isConfidential()) {
-            return false;
-        }
-        {
-            final String valueDebug = value.toDebugString(locale);
-            if (valueDebug != null && valueDebug.toLowerCase().contains(lowerSearchTerm)) {
-                return true;
-            }
-        }
-        if (PwmSettingSyntax.SELECT == setting.getSyntax()
-                || PwmSettingSyntax.OPTIONLIST == setting.getSyntax()
-                || PwmSettingSyntax.VERIFICATION_METHOD == setting.getSyntax()
-                ) {
-            for (final String key : setting.getOptions().keySet()) {
-                if (key.toLowerCase().contains(lowerSearchTerm)) {
-                    return true;
-                }
-                final String optionValue = setting.getOptions().get(key);
-                if (optionValue != null && optionValue.toLowerCase().contains(lowerSearchTerm)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-
-    public StoredValue readSetting(final PwmSetting setting) {
-        return readSetting(setting,null);
-    }
-
-    public StoredValue readSetting(final PwmSetting setting, final String profileID) {
-        if (profileID == null && setting.getCategory().hasProfiles()) {
-            IllegalArgumentException e = new IllegalArgumentException("reading of setting " + setting.getKey() + " requires a non-null profileID");
-            LOGGER.error("error",e);
-            throw e;
-        }
-        if (profileID != null && !setting.getCategory().hasProfiles()) {
-            throw new IllegalStateException("cannot read setting key " + setting.getKey() + " with non-null profileID");
-        }
-        domModifyLock.readLock().lock();
-        try {
-            final XPathExpression xp = XPathBuilder.xpathForSetting(setting, profileID);
-            final Element settingElement = (Element)xp.evaluateFirst(document);
-
-            if (settingElement == null) {
-                return defaultValue(setting, getTemplate());
-            }
-
-            if (settingElement.getChild(XML_ELEMENT_DEFAULT) != null) {
-                return defaultValue(setting, getTemplate());
-            }
-
-            try {
-                return ValueFactory.fromXmlValues(setting, settingElement, getKey());
-            } catch (PwmException e) {
-                final String errorMsg = "unexpected error reading setting '" + setting.getKey() + "' profile '" + profileID + "', error: " + e.getMessage();
-                throw new IllegalStateException(errorMsg);
-            }
-        } finally {
-            domModifyLock.readLock().unlock();
-        }
-    }
-
-    public void writeLocaleBundleMap(final String bundleName, final String keyName, final Map<String,String> localeMap) {
-        ResourceBundle theBundle = null;
-        for (final PwmLocaleBundle bundle : PwmLocaleBundle.values()) {
-            if (bundle.getTheClass().getName().equals(bundleName)) {
-                theBundle = ResourceBundle.getBundle(bundleName);
-            }
-        }
-
-        if (theBundle == null) {
-            LOGGER.info("ignoring unknown locale bundle for bundle=" + bundleName + ", key=" + keyName);
-            return;
-        }
-
-        if (theBundle.getString(keyName) == null) {
-            LOGGER.info("ignoring unknown key for bundle=" + bundleName + ", key=" + keyName);
-            return;
-        }
-
-
-        resetLocaleBundleMap(bundleName, keyName);
-        if (localeMap == null || localeMap.isEmpty()) {
-            LOGGER.info("cleared locale bundle map for bundle=" + bundleName + ", key=" + keyName);
-            return;
-        }
-
-        preModifyActions();
-        changeLog.updateChangeLog(bundleName, keyName, localeMap);
-        try {
-            domModifyLock.writeLock().lock();
-            final Element localeBundleElement = new Element("localeBundle");
-            localeBundleElement.setAttribute("bundle",bundleName);
-            localeBundleElement.setAttribute("key",keyName);
-            for (final String locale : localeMap.keySet()) {
-                final Element valueElement = new Element("value");
-                if (locale != null && locale.length() > 0) {
-                    valueElement.setAttribute("locale",locale);
-                }
-                valueElement.setContent(new CDATA(localeMap.get(locale)));
-                localeBundleElement.addContent(valueElement);
-            }
-            localeBundleElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-            document.getRootElement().addContent(localeBundleElement);
-        } finally {
-            domModifyLock.writeLock().unlock();
-        }
-    }
-
-    public void copyProfileID(final PwmSettingCategory category, final String sourceID, final String destinationID, final UserIdentity userIdentity)
-            throws PwmUnrecoverableException
-    {
-        if (!category.hasProfiles()) {
-            throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category " + category + ", category does not have profiles");
-        }
-        final List<String> existingProfiles = this.profilesForSetting(category.getProfileSetting());
-        if (!existingProfiles.contains(sourceID)) {
-            throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, source profileID '" + sourceID + "' does not exist");
-        }
-        if (existingProfiles.contains(destinationID)) {
-            throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, destination profileID '" + destinationID+ "' already exists");
-        }
-        for (final PwmSetting pwmSetting : category.getSettings()) {
-            if (!isDefaultValue(pwmSetting, sourceID)) {
-                final StoredValue value = readSetting(pwmSetting, sourceID);
-                writeSetting(pwmSetting, destinationID, value, userIdentity);
-            }
-        }
-        final List<String> newProfileIDList = new ArrayList<>();
-        newProfileIDList.addAll(existingProfiles);
-        newProfileIDList.add(destinationID);
-        writeSetting(category.getProfileSetting(), new StringArrayValue(newProfileIDList), userIdentity);
-    }
-
-
-    public void writeSetting(
-            final PwmSetting setting,
-            final StoredValue value,
-            final UserIdentity userIdentity
-    ) throws PwmUnrecoverableException {
-        writeSetting(setting, null, value, userIdentity);
-    }
-
-    public void writeSetting(
-            final PwmSetting setting,
-            final String profileID,
-            final StoredValue value,
-            final UserIdentity userIdentity
-    ) throws PwmUnrecoverableException {
-        if (profileID == null && setting.getCategory().hasProfiles()) {
-            throw new IllegalArgumentException("reading of setting " + setting.getKey() + " requires a non-null profileID");
-        }
-        if (profileID != null && !setting.getCategory().hasProfiles()) {
-            throw new IllegalArgumentException("cannot specify profile for non-profile setting");
-        }
-
-        preModifyActions();
-        changeLog.updateChangeLog(setting, profileID, value);
-        domModifyLock.writeLock().lock();
-        try {
-            final Element settingElement = createOrGetSettingElement(document, setting, profileID);
-            settingElement.removeContent();
-            settingElement.setAttribute(XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString());
-            settingElement.setAttribute(XML_ATTRIBUTE_SYNTAX_VERSION, Integer.toString(value.currentSyntaxVersion()));
-
-            if (setting_writeLabels) {
-                final Element labelElement = new Element("label");
-                labelElement.addContent(setting.getLabel(PwmConstants.DEFAULT_LOCALE));
-                settingElement.addContent(labelElement);
-            }
-
-            if (setting.getSyntax() == PwmSettingSyntax.PASSWORD) {
-                final List<Element> valueElements = ((PasswordValue)value).toXmlValues("value", getKey());
-                settingElement.addContent(new Comment("Note: This value is encrypted and can not be edited directly."));
-                settingElement.addContent(new Comment("Please use the Configuration Manager GUI to modify this value."));
-                settingElement.addContent(valueElements);
-            } else {
-                settingElement.addContent(value.toXmlValues("value"));
-            }
-
-            updateMetaData(settingElement, userIdentity);
-        } finally {
-            domModifyLock.writeLock().unlock();
-        }
-    }
-
-    public String settingChecksum()
-            throws PwmUnrecoverableException
-    {
-        final Date startTime = new Date();
-
-        final List<SettingValueRecord> modifiedSettings = modifiedSettings();
-        final StringBuilder sb = new StringBuilder();
-        sb.append("PwmSettingsChecksum");
-        for (SettingValueRecord settingValueRecord : modifiedSettings) {
-            final StoredValue storedValue = settingValueRecord.getStoredValue();
-            sb.append(storedValue.valueHash());
-        }
-
-
-        final String result = SecureEngine.hash(sb.toString(), PwmConstants.SETTING_CHECKSUM_HASH_METHOD);
-        LOGGER.trace("computed setting checksum in " + TimeDuration.fromCurrent(startTime).asCompactString());
-        return result;
-    }
-
-
-    private void preModifyActions() {
-        if (locked) {
-            throw new UnsupportedOperationException("StoredConfiguration is locked and cannot be modified");
-        }
-        document.getRootElement().setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-    }
-
-// -------------------------- INNER CLASSES --------------------------
-
-    public void setPassword(final String password)
-            throws PwmOperationalException
-    {
-        if (password == null || password.isEmpty()) {
-            throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"can not set blank password"}));
-        }
-        final String trimmedPassword = password.trim();
-        if (trimmedPassword.length() < 1) {
-            throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"can not set blank password"}));
-        }
-
-
-        final String salt = BCrypt.gensalt();
-        final String passwordHash = BCrypt.hashpw(password,salt);
-        this.writeConfigProperty(ConfigProperty.PROPERTY_KEY_PASSWORD_HASH, passwordHash);
-    }
-
-    public boolean verifyPassword(final String password) {
-        if (!hasPassword()) {
-            return false;
-        }
-        final String passwordHash = this.readConfigProperty(ConfigProperty.PROPERTY_KEY_PASSWORD_HASH);
-        return BCrypt.checkpw(password,passwordHash);
-    }
-
-    public boolean hasPassword() {
-        final String passwordHash = this.readConfigProperty(ConfigProperty.PROPERTY_KEY_PASSWORD_HASH);
-        return passwordHash != null && passwordHash.length() > 0;
-    }
-
-    private static abstract class XPathBuilder {
-        private static XPathExpression xpathForLocaleBundleSetting(final String bundleName, final String keyName) {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//localeBundle[@bundle=\"" + bundleName + "\"][@key=\"" + keyName + "\"]";
-            return xpfac.compile(xpathString);
-        }
-
-        private static XPathExpression xpathForSetting(final PwmSetting setting, final String profileID) {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            if (profileID == null || profileID.length() < 1) {
-                xpathString = "//setting[@key=\"" + setting.getKey() + "\"][(not (@profile)) or @profile=\"\"]";
-            } else {
-                xpathString = "//setting[@key=\"" + setting.getKey() + "\"][@profile=\"" + profileID + "\"]";
-            }
-
-            return xpfac.compile(xpathString);
-        }
-
-        private static XPathExpression xpathForAppProperty(final AppProperty appProperty) {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]/"
-                    + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + appProperty.getKey() + "\"]";
-            return xpfac.compile(xpathString);
-        }
-
-        private static XPathExpression xpathForAppProperties() {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]";
-            return xpfac.compile(xpathString);
-        }
-
-        private static XPathExpression xpathForConfigProperty(final ConfigProperty configProperty) {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]/"
-                    + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + configProperty.getKey() + "\"]";
-            return xpfac.compile(xpathString);
-        }
-
-        private static XPathExpression xpathForConfigProperties() {
-            final XPathFactory xpfac = XPathFactory.instance();
-            final String xpathString;
-            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]";
-            return xpfac.compile(xpathString);
-        }
-    }
-
-
-    private static class ConfigurationCleaner {
-        private static void cleanup(final StoredConfiguration configuration) throws PwmUnrecoverableException {
-            updateProperitiesWithoutType(configuration);
-            updateMandatoryElements(configuration.document);
-            profilizeNonProfiledSettings(configuration);
-            stripOrphanedProfileSettings(configuration);
-            migrateAppProperties(configuration);
-            updateDeprecatedSettings(configuration);
-        }
-
-        private static void updateMandatoryElements(final Document document) {
-            final Element rootElement = document.getRootElement();
-
-            {
-                final XPathExpression commentXPath = XPathFactory.instance().compile("//comment()[1]");
-                final Comment existingComment = (Comment)commentXPath.evaluateFirst(rootElement);
-                if (existingComment != null) {
-                    existingComment.detach();
-                }
-                final Comment comment = new Comment(generateCommentText());
-                rootElement.addContent(0,comment);
-            }
-
-            rootElement.setAttribute("pwmVersion", PwmConstants.BUILD_VERSION);
-            rootElement.setAttribute("pwmBuild", PwmConstants.BUILD_NUMBER);
-            rootElement.setAttribute("pwmBuildType", PwmConstants.BUILD_TYPE);
-            rootElement.setAttribute("xmlVersion", XML_FORMAT_VERSION);
-
-            { // migrate old properties
-
-                // read correct (new) //properties[@type="config"]
-                final XPathExpression configPropertiesXpath = XPathFactory.instance().compile(
-                        "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]");
-                final Element configPropertiesElement = (Element)configPropertiesXpath.evaluateFirst(rootElement);
-
-                // read list of old //properties[not (@type)]/property
-                final XPathExpression nonAttributedProperty = XPathFactory.instance().compile(
-                        "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]/" + XML_ELEMENT_PROPERTY);
-                final List<Element> nonAttributedProperties = nonAttributedProperty.evaluate(rootElement);
-
-                if (configPropertiesElement != null && nonAttributedProperties != null) {
-                    for (Element element : nonAttributedProperties) {
-                        element.detach();
-                        configPropertiesElement.addContent(element);
-                    }
-                }
-
-                // remove old //properties[not (@type] element
-                final XPathExpression oldPropertiesXpath = XPathFactory.instance().compile(
-                        "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]");
-                final List<Element> oldPropertiesElements = oldPropertiesXpath.evaluate(rootElement);
-                if (oldPropertiesElements != null) {
-                    for (Element element : oldPropertiesElements) {
-                        element.detach();
-                    }
-                }
-            }
-        }
-
-        private static String generateCommentText() {
-            final StringBuilder commentText = new StringBuilder();
-            commentText.append("\t\t").append(" ").append("\n");
-            commentText.append("\t\t").append("This configuration file has been auto-generated by the ").append(PwmConstants.PWM_APP_NAME).append(" password self service application.").append("\n");
-            commentText.append("\t\t").append("").append("\n");
-            commentText.append("\t\t").append("WARNING: This configuration file contains sensitive security information, please handle with care!").append("\n");
-            commentText.append("\t\t").append("").append("\n");
-            commentText.append("\t\t").append("WARNING: If a server is currently running using this configuration file, it will be restarted").append("\n");
-            commentText.append("\t\t").append("         and the configuration updated immediately when it is modified.").append("\n");
-            commentText.append("\t\t").append("").append("\n");
-            commentText.append("\t\t").append("NOTICE: This file is encoded as UTF-8.  Do not save or edit this file with an editor that does not").append("\n");
-            commentText.append("\t\t").append("        support UTF-8 encoding.").append("\n");
-            commentText.append("\t\t").append("").append("\n");
-            commentText.append("\t\t").append("If unable to edit using the application ConfigurationEditor web UI, the following options are available.").append("\n");
-            commentText.append("\t\t").append("   or 1. Edit this file directly by hand.").append("\n");
-            commentText.append("\t\t").append("   or 2. Unlock the configuration by setting the property 'configIsEditable' to 'true' in this file.  This will ").append("\n");
-            commentText.append("\t\t").append("         allow access to the ConfigurationEditor web UI without having to authenticate to an LDAP server first.").append("\n");
-            commentText.append("\t\t").append("   or 3. Unlock the configuration by using the the command line utility. ").append("\n");
-            commentText.append("\t\t").append("").append("\n");
-            return commentText.toString();
-        }
-
-
-        private static void profilizeNonProfiledSettings(final StoredConfiguration storedConfiguration) throws PwmUnrecoverableException {
-            final String NEW_PROFILE_NAME = "default";
-            final Document document = storedConfiguration.document;
-            for (final PwmSetting setting : PwmSetting.values()) {
-                if (setting.getCategory().hasProfiles()) {
-
-                    final XPathExpression xp = XPathBuilder.xpathForSetting(setting, null);
-                    final Element settingElement = (Element)xp.evaluateFirst(document);
-                    if (settingElement != null) {
-                        LOGGER.info("moving setting " + setting.getKey() + " without profile attribute to profile \"" + NEW_PROFILE_NAME + "\".");
-                        // change setting to "default" profile.
-                        settingElement.setAttribute(XML_ATTRIBUTE_PROFILE, NEW_PROFILE_NAME);
-
-                        final PwmSetting profileSetting = setting.getCategory().getProfileSetting();
-                        final List<String> profileStringDefinitions = new ArrayList<>();
-                        {
-                            final StringArrayValue profileDefinitions = (StringArrayValue) storedConfiguration.readSetting(profileSetting);
-                            if (profileDefinitions != null) {
-                                if (profileDefinitions.toNativeObject() != null) {
-                                    profileStringDefinitions.addAll(profileDefinitions.toNativeObject());
-                                }
-                            }
-                        }
-
-                        if (!profileStringDefinitions.contains(NEW_PROFILE_NAME)) {
-                            profileStringDefinitions.add(NEW_PROFILE_NAME);
-                            storedConfiguration.writeSetting(profileSetting,new StringArrayValue(profileStringDefinitions),null);
-                        }
-                    }
-                }
-            }
-        }
-
-        private static void updateProperitiesWithoutType(final StoredConfiguration storedConfiguration) {
-            final Document document = storedConfiguration.document;
-            final String xpathString = "//properties[not(@type)]";
-            final XPathFactory xpfac = XPathFactory.instance();
-            final XPathExpression xp = xpfac.compile(xpathString);
-            final List<Element> propertiesElements = (List<Element>)xp.evaluate(document);
-            for (final Element propertiesElement : propertiesElements) {
-                propertiesElement.setAttribute(XML_ATTRIBUTE_TYPE,XML_ATTRIBUTE_VALUE_CONFIG);
-            }
-        }
-
-        private static void stripOrphanedProfileSettings(final StoredConfiguration storedConfiguration) {
-            final Document document = storedConfiguration.document;
-            final XPathFactory xpfac = XPathFactory.instance();
-            for (final PwmSetting setting : PwmSetting.values()) {
-                if (setting.getCategory().hasProfiles()) {
-                    final List<String> validProfiles = storedConfiguration.profilesForSetting(setting);
-                    final String xpathString = "//setting[@key=\"" + setting.getKey() + "\"]";
-                    final XPathExpression xp = xpfac.compile(xpathString);
-                    final List<Element> settingElements = (List<Element>)xp.evaluate(document);
-                    for (final Element settingElement : settingElements) {
-                        final String profileID = settingElement.getAttributeValue(XML_ATTRIBUTE_PROFILE);
-                        if (profileID != null) {
-                            if (!validProfiles.contains(profileID)) {
-                                LOGGER.info("removing setting " + setting.getKey() + " with profile \"" + profileID + "\", profile is not a valid profile");
-                                settingElement.detach();
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        private static void migrateAppProperties(final StoredConfiguration storedConfiguration) throws PwmUnrecoverableException {
-            final Document document = storedConfiguration.document;
-            final XPathExpression xPathExpression = XPathBuilder.xpathForAppProperties();
-            final List<Element> appPropertiesElements = (List<Element>)xPathExpression.evaluate(document);
-            for (final Element element : appPropertiesElements) {
-                final List<Element> properties = element.getChildren();
-                for (final Element property : properties) {
-                    final String key = property.getAttributeValue("key");
-                    final String value = property.getText();
-                    if (key != null && !key.isEmpty() && value != null && !value.isEmpty()) {
-                        LOGGER.info("migrating app-property config element '" + key + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey());
-                        final String newValue = key + "=" + value;
-                        List<String> existingValues = (List<String>)storedConfiguration.readSetting(PwmSetting.APP_PROPERTY_OVERRIDES).toNativeObject();
-                        if (existingValues == null) {
-                            existingValues = new ArrayList<>();
-                        }
-                        existingValues = new ArrayList<>(existingValues);
-                        existingValues.add(newValue);
-                        storedConfiguration.writeSetting(PwmSetting.APP_PROPERTY_OVERRIDES,new StringArrayValue(existingValues),null);
-                    }
-                }
-                element.detach();
-            }
-        }
-
-        private static void updateDeprecatedSettings(final StoredConfiguration storedConfiguration) throws PwmUnrecoverableException {
-            final UserIdentity actor = new UserIdentity("UpgradeProcessor", null);
-            for (final String profileID : storedConfiguration.profilesForSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY)) {
-                if (!storedConfiguration.isDefaultValue(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID)) {
-                    boolean ad2003Enabled = (boolean) storedConfiguration.readSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY,profileID).toNativeObject();
-                    final StoredValue value;
-                    if (ad2003Enabled) {
-                        value = new StringValue(ADPolicyComplexity.AD2003.toString());
-                    } else {
-                        value = new StringValue(ADPolicyComplexity.NONE.toString());
-                    }
-                    LOGGER.warn("converting deprecated non-default setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
-                            + " to replacement setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + value.toNativeObject().toString());
-                    storedConfiguration.writeSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, actor);
-                    storedConfiguration.resetSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID, actor);
-                }
-            }
-
-            /*
-            {
-                if (!storedConfiguration.isDefaultValue(PwmSetting.CHALLENGE_REQUIRE_RESPONSES)) {
-                    final StoredValue configValue = storedConfiguration.readSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default");
-                    final VerificationMethodValue.VerificationMethodSettings existingSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
-                    final Map<RecoveryVerificationMethod,VerificationMethodValue.VerificationMethodSetting> newMethods = new HashMap<>();
-                    newMethods.putAll(existingSettings.getMethodSettings());
-                    VerificationMethodValue.VerificationMethodSetting setting = new VerificationMethodValue.VerificationMethodSetting(VerificationMethodValue.EnabledState.disabled);
-                    newMethods.put(RecoveryVerificationMethod.CHALLENGE_RESPONSES,setting);
-                    final VerificationMethodValue.VerificationMethodSettings newSettings = new VerificationMethodValue.VerificationMethodSettings(
-                            newMethods,
-                            existingSettings.getMinOptionalRequired()
-                    );
-                    storedConfiguration.writeSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default", new VerificationMethodValue(newSettings), actor);
-                }
-            }
-
-            {
-                if (!storedConfiguration.isDefaultValue(PwmSetting.FORGOTTEN_PASSWORD_REQUIRE_OTP)) {
-                    final StoredValue configValue = storedConfiguration.readSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default");
-                    final VerificationMethodValue.VerificationMethodSettings existingSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
-                    final Map<RecoveryVerificationMethod,VerificationMethodValue.VerificationMethodSetting> newMethods = new HashMap<>();
-                    newMethods.putAll(existingSettings.getMethodSettings());
-                    VerificationMethodValue.VerificationMethodSetting setting = new VerificationMethodValue.VerificationMethodSetting(VerificationMethodValue.EnabledState.required);
-                    newMethods.put(RecoveryVerificationMethod.CHALLENGE_RESPONSES,setting);
-                    final VerificationMethodValue.VerificationMethodSettings newSettings = new VerificationMethodValue.VerificationMethodSettings(
-                            newMethods,
-                            existingSettings.getMinOptionalRequired()
-                    );
-                    storedConfiguration.writeSetting(PwmSetting.FORGOTTEN_PASSWORD_REQUIRE_OTP, "default", new VerificationMethodValue(newSettings), actor);
-                }
-            }
-            */
-        }
-    }
-
-
-    public static class ConfigRecordID implements Serializable {
-        private RecordType recordType;
-        private Object recordID;
-        private String profileID;
-
-        public enum RecordType {
-            SETTING,
-            LOCALE_BUNDLE,
-        }
-
-        public ConfigRecordID(
-                RecordType recordType,
-                Object recordID,
-                String profileID
-        )
-        {
-            this.recordType = recordType;
-            this.recordID = recordID;
-            this.profileID = profileID;
-        }
-
-        @Override
-        public boolean equals(Object o)
-        {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-
-            ConfigRecordID that = (ConfigRecordID) o;
-
-            if (profileID != null ? !profileID.equals(that.profileID) : that.profileID != null) return false;
-            if (recordID != null ? !recordID.equals(that.recordID) : that.recordID != null) return false;
-            if (recordType != that.recordType) return false;
-
-            return true;
-        }
-
-        @Override
-        public int hashCode()
-        {
-            int result = recordType != null ? recordType.hashCode() : 0;
-            result = 31 * result + (recordID != null ? recordID.hashCode() : 0);
-            result = 31 * result + (profileID != null ? profileID.hashCode() : 0);
-            return result;
-        }
-
-        public RecordType getRecordType()
-        {
-            return recordType;
-        }
-
-        public Object getRecordID()
-        {
-            return recordID;
-        }
-
-        public String getProfileID()
-        {
-            return profileID;
-        }
-    }
-
-    public String changeLogAsDebugString(final Locale locale, final boolean asHtml) {
-        return changeLog.changeLogAsDebugString(locale, asHtml);
-    }
-
-    private PwmSecurityKey cachedKey = null;
-    public PwmSecurityKey getKey() throws PwmUnrecoverableException {
-        if (cachedKey == null) {
-            cachedKey = new PwmSecurityKey(createTime() + StoredConfiguration.class.getSimpleName());
-        }
-        return cachedKey;
-    }
-
-    public boolean isModified() {
-        return changeLog.isModified();
-    }
-
-    private class ChangeLog implements Serializable {
-        /* values contain the _original_ toJson version of the value. */
-        private Map<ConfigRecordID,String> changeLog = new LinkedHashMap<>();
-
-        public boolean isModified() {
-            return !changeLog.isEmpty();
-        }
-
-        public String changeLogAsDebugString(final Locale locale, boolean asHtml) {
-            final Map<String,String> outputMap = new TreeMap<>();
-            final String SEPARATOR = LocaleHelper.getLocalizedMessage(locale, Config.Display_SettingNavigationSeparator, null);
-
-            for (final ConfigRecordID configRecordID : changeLog.keySet()) {
-                switch (configRecordID.recordType) {
-                    case SETTING: {
-                        final StoredValue currentValue = readSetting((PwmSetting) configRecordID.recordID, configRecordID.profileID);
-                        final PwmSetting pwmSetting = (PwmSetting) configRecordID.recordID;
-                        final String keyName = pwmSetting.toMenuLocationDebug(configRecordID.getProfileID(), locale);
-                        final String debugValue = currentValue.toDebugString(locale);
-                        outputMap.put(keyName,debugValue);
-                    }
-                    break;
-
-                    case LOCALE_BUNDLE: {
-                        final String key = (String) configRecordID.recordID;
-                        final String bundleName = key.split("!")[0];
-                        final String keys = key.split("!")[1];
-                        final Map<String,String> currentValue = readLocaleBundleMap(bundleName,keys);
-                        final String debugValue = JsonUtil.serializeMap(currentValue, JsonUtil.Flag.PrettyPrint);
-                        outputMap.put("LocaleBundle" + SEPARATOR + bundleName + " " + keys,debugValue);
-                    }
-                    break;
-                }
-            }
-            final StringBuilder output = new StringBuilder();
-            if (outputMap.isEmpty()) {
-                output.append("No setting changes.");
-            } else {
-                for (final String keyName : outputMap.keySet()) {
-                    final String value = outputMap.get(keyName);
-                    if (asHtml) {
-                        output.append("<div class=\"changeLogKey\">");
-                        output.append(keyName);
-                        output.append("</div><div class=\"changeLogValue\">");
-                        output.append(StringUtil.escapeHtml(value));
-                        output.append("</div>");
-                    } else {
-                        output.append(keyName);
-                        output.append("\n");
-                        output.append(" Value: ");
-                        output.append(value);
-                        output.append("\n");
-                    }
-                }
-            }
-            return output.toString();
-        }
-
-        public void updateChangeLog(final String bundleName, final String keyName, final Map<String,String> localeValueMap) {
-            final String key = bundleName + "!" + keyName;
-            final Map<String,String> currentValue = readLocaleBundleMap(bundleName, keyName);
-            final String currentJsonValue = JsonUtil.serializeMap(currentValue);
-            final String newJsonValue = JsonUtil.serializeMap(localeValueMap);
-            final ConfigRecordID configRecordID = new ConfigRecordID(ConfigRecordID.RecordType.LOCALE_BUNDLE, key, null);
-            updateChangeLog(configRecordID,currentJsonValue,newJsonValue);
-        }
-
-        public void updateChangeLog(final PwmSetting setting, final String profileID, final StoredValue newValue) {
-            final StoredValue currentValue = readSetting(setting, profileID);
-            final String currentJsonValue = JsonUtil.serialize(currentValue);
-            final String newJsonValue = JsonUtil.serialize(newValue);
-            final ConfigRecordID configRecordID = new ConfigRecordID(ConfigRecordID.RecordType.SETTING, setting, profileID);
-            updateChangeLog(configRecordID,currentJsonValue,newJsonValue);
-        }
-
-        public void updateChangeLog(final ConfigRecordID configRecordID, final String currentValueString, final String newValueString) {
-            if (changeLog.containsKey(configRecordID)) {
-                final String currentRecord = changeLog.get(configRecordID);
-
-                if (currentRecord == null && newValueString == null) {
-                    changeLog.remove(configRecordID);
-                } else if (currentRecord != null && currentRecord.equals(newValueString)) {
-                    changeLog.remove(configRecordID);
-                }
-            } else {
-                changeLog.put(configRecordID,currentValueString);
-            }
-        }
-    }
-
-    public static void validateXmlSchema(final String xmlDocument)
-            throws PwmUnrecoverableException
-    {
-        return;
-                /*
-        try {
-            final InputStream xsdInputStream = PwmSetting.class.getClassLoader().getResourceAsStream("password/pwm/config/StoredConfiguration.xsd");
-            final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-            final Schema schema = factory.newSchema(new StreamSource(xsdInputStream));
-            Validator validator = schema.newValidator();
-            validator.validate(new StreamSource(new StringReader(xmlDocument)));
-        } catch (Exception e) {
-            final String errorMsg = "error while validating setting file schema definition: " + e.getMessage();
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg));
-        }
-        */
-    }
-
-    private static void updateMetaData(final Element settingElement, final UserIdentity userIdentity) {
-        final Element settingsElement = settingElement.getDocument().getRootElement().getChild(XML_ELEMENT_SETTINGS);
-        settingElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-        settingsElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
-        settingElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
-        settingsElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
-        if (userIdentity != null) {
-            settingElement.setAttribute(XML_ATTRIBUTE_MODIFY_USER, userIdentity.toDelimitedKey());
-            settingsElement.setAttribute(XML_ATTRIBUTE_MODIFY_USER, userIdentity.toDelimitedKey());
-        }
-    }
-
-    private static Element createOrGetSettingElement(
-            final Document document,
-            final PwmSetting setting,
-            final String profileID
-    ) {
-        final XPathExpression xp = XPathBuilder.xpathForSetting(setting, profileID);
-        final Element existingSettingElement = (Element)xp.evaluateFirst(document);
-        if (existingSettingElement != null) {
-            return existingSettingElement;
-        }
-
-        final Element settingElement = new Element(XML_ELEMENT_SETTING);
-        settingElement.setAttribute(XML_ATTRIBUTE_KEY, setting.getKey());
-        settingElement.setAttribute(XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString());
-        if (profileID != null && profileID.length() > 0) {
-            settingElement.setAttribute(XML_ATTRIBUTE_PROFILE, profileID);
-        }
-
-        Element settingsElement = document.getRootElement().getChild(XML_ELEMENT_SETTINGS);
-        if (settingsElement == null) {
-            settingsElement = new Element(XML_ELEMENT_SETTINGS);
-            document.getRootElement().addContent(settingsElement);
-        }
-        settingsElement.addContent(settingElement);
-
-        return settingElement;
-    }
-
-    static class SettingValueRecord implements Serializable {
-        private PwmSetting setting;
-        private String profile;
-        private StoredValue storedValue;
-
-        public SettingValueRecord(
-                PwmSetting setting,
-                String profile,
-                StoredValue storedValue
-        )
-        {
-            this.setting = setting;
-            this.profile = profile;
-            this.storedValue = storedValue;
-        }
-
-        public PwmSetting getSetting()
-        {
-            return setting;
-        }
-
-        public String getProfile()
-        {
-            return profile;
-        }
-
-        public StoredValue getStoredValue()
-        {
-            return storedValue;
-        }
-    }
-
-    class StoredValueIterator implements Iterator<StoredConfiguration.SettingValueRecord> {
-
-        private Queue<SettingValueRecord> settingQueue = new LinkedList<>();
-
-        public StoredValueIterator(boolean includeDefaults) {
-            for (final PwmSetting setting : PwmSetting.values()) {
-                if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
-                    if (includeDefaults || !isDefaultValue(setting)) {
-                        SettingValueRecord settingValueRecord = new SettingValueRecord(setting, null, null);
-                        settingQueue.add(settingValueRecord);
-                    }
-                }
-            }
-
-            for (final PwmSettingCategory category : PwmSettingCategory.values()) {
-                if (category.hasProfiles()) {
-                    for (final String profileID : profilesForSetting(category.getProfileSetting())) {
-                        for (final PwmSetting setting : category.getSettings()) {
-                            if (includeDefaults || !isDefaultValue(setting,profileID)) {
-                                SettingValueRecord settingValueRecord = new SettingValueRecord(setting, profileID, null);
-                                settingQueue.add(settingValueRecord);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-
-        @Override
-        public boolean hasNext()
-        {
-            return !settingQueue.isEmpty();
-        }
-
-        @Override
-        public SettingValueRecord next()
-        {
-            StoredConfiguration.SettingValueRecord settingValueRecord = settingQueue.poll();
-            return new SettingValueRecord(
-                    settingValueRecord.getSetting(),
-                    settingValueRecord.getProfile(),
-                    readSetting(settingValueRecord.getSetting(),settingValueRecord.getProfile())
-            );
-        }
-
-        @Override
-        public void remove()
-        {
-
-        }
-    }
-
-    private String createTime() {
-        final Element rootElement = document.getRootElement();
-        final String createTimeString = rootElement.getAttributeValue(XML_ATTRIBUTE_CREATE_TIME);
-        if (createTimeString == null || createTimeString.isEmpty()) {
-            throw new IllegalStateException("missing createTime timestamp");
-        }
-        return createTimeString;
-    }
-
-    public Date modifyTime() {
-        final Element rootElement = document.getRootElement();
-        final String modifyTimeString = rootElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME);
-        if (modifyTimeString != null) {
-            try {
-                return PwmConstants.DEFAULT_DATETIME_FORMAT.parse(modifyTimeString);
-            } catch (ParseException e) {
-                LOGGER.error("error parsing root last modified timestamp: " + e.getMessage());
-            }
-        }
-        return null;
-    }
-
-    public void initNewRandomSecurityKey()
-            throws PwmUnrecoverableException
-    {
-        if (!isDefaultValue(PwmSetting.PWM_SECURITY_KEY)) {
-            return;
-        }
-
-        writeSetting(
-                PwmSetting.PWM_SECURITY_KEY,
-                new PasswordValue(new PasswordData(PwmRandom.getInstance().alphaNumericString(1024))),
-                null
-        );
-
-        LOGGER.debug("initialized new random security key");
-    }
-
-}
+/*
+ * Password Management Servlets (PWM)
+ * http://code.google.com/p/pwm/
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2015 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.stored;
+
+import org.jdom2.*;
+import org.jdom2.xpath.XPathExpression;
+import org.jdom2.xpath.XPathFactory;
+import password.pwm.AppProperty;
+import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.*;
+import password.pwm.config.option.ADPolicyComplexity;
+import password.pwm.config.value.*;
+import password.pwm.error.*;
+import password.pwm.i18n.Config;
+import password.pwm.i18n.LocaleHelper;
+import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.*;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmRandom;
+import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.SecureEngine;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.text.ParseException;
+import java.util.*;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * @author Jason D. Rivard
+ */
+public class StoredConfigurationImpl implements Serializable, StoredConfiguration {
+// ------------------------------ FIELDS ------------------------------
+
+    public enum ConfigProperty {
+        PROPERTY_KEY_CONFIG_IS_EDITABLE("configIsEditable"),
+        PROPERTY_KEY_CONFIG_EPOCH("configEpoch"),
+        PROPERTY_KEY_TEMPLATE("configTemplate"),
+        PROPERTY_KEY_NOTES("notes"),
+        PROPERTY_KEY_PASSWORD_HASH("configPasswordHash"),
+        PROPERTY_KEY_SAVE_CONFIG_ON_START("saveConfigOnStart"),
+        ;
+
+        private final String key;
+
+        private ConfigProperty(String key)
+        {
+            this.key = key;
+        }
+
+        public String getKey()
+        {
+            return key;
+        }
+    }
+
+    public static class SettingMetaData implements Serializable {
+        private Date modifyDate;
+        private UserIdentity userIdentity;
+
+        public Date getModifyDate()
+        {
+            return modifyDate;
+        }
+
+        public UserIdentity getUserIdentity()
+        {
+            return userIdentity;
+        }
+    }
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass(StoredConfigurationImpl.class);
+    private static final String XML_FORMAT_VERSION = "4";
+
+    public static final String XML_ELEMENT_ROOT = "PwmConfiguration";
+    public static final String XML_ELEMENT_PROPERTIES = "properties";
+    public static final String XML_ELEMENT_PROPERTY = "property";
+    public static final String XML_ELEMENT_SETTINGS = "settings";
+    public static final String XML_ELEMENT_SETTING = "setting";
+    public static final String XML_ELEMENT_DEFAULT = "default";
+
+    public static final String XML_ATTRIBUTE_TYPE = "type";
+    public static final String XML_ATTRIBUTE_KEY = "key";
+    public static final String XML_ATTRIBUTE_SYNTAX = "syntax";
+    public static final String XML_ATTRIBUTE_PROFILE = "profile";
+    public static final String XML_ATTRIBUTE_VALUE_APP = "app";
+    public static final String XML_ATTRIBUTE_VALUE_CONFIG = "config";
+    public static final String XML_ATTRIBUTE_CREATE_TIME = "createTime";
+    public static final String XML_ATTRIBUTE_MODIFY_TIME = "modifyTime";
+    public static final String XML_ATTRIBUTE_MODIFY_USER = "modifyUser";
+    public static final String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion";
+
+    private Document document = new Document(new Element(XML_ELEMENT_ROOT));
+    private ChangeLog changeLog = new ChangeLog();
+
+    private boolean locked = false;
+    private boolean setting_writeLabels = true;
+    private final ReentrantReadWriteLock domModifyLock = new ReentrantReadWriteLock();
+
+// -------------------------- STATIC METHODS --------------------------
+
+    public static StoredConfigurationImpl newStoredConfiguration() throws PwmUnrecoverableException {
+        return new StoredConfigurationImpl();
+    }
+
+    public static StoredConfigurationImpl copy(final StoredConfigurationImpl input) throws PwmUnrecoverableException {
+        final StoredConfigurationImpl copy = new StoredConfigurationImpl();
+        copy.document = input.document.clone();
+        return copy;
+    }
+
+    public static StoredConfigurationImpl fromXml(final InputStream xmlData)
+            throws PwmUnrecoverableException
+    {
+        final Date startTime = new Date();
+        //validateXmlSchema(xmlData);
+
+        final Document inputDocument = XmlUtil.parseXml(xmlData);
+        final StoredConfigurationImpl newConfiguration = StoredConfigurationImpl.newStoredConfiguration();
+
+        try {
+            newConfiguration.document = inputDocument;
+            newConfiguration.createTime(); // verify create time;
+            ConfigurationCleaner.cleanup(newConfiguration);
+        } catch (Exception e) {
+            final String errorMsg = "error reading configuration file format, error=" + e.getMessage();
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg});
+            throw new PwmUnrecoverableException(errorInfo);
+        }
+
+        checkIfXmlRequiresUpdate(newConfiguration);
+        final TimeDuration totalDuration = TimeDuration.fromCurrent(startTime);
+        LOGGER.debug("successfully loaded configuration (" + totalDuration.asCompactString() + ")");
+        return newConfiguration;
+    }
+
+    /**
+     * Loop through all settings to see if setting value has flag {@link StoredValue#requiresStoredUpdate()} set to true.
+     * If so, then call {@link #writeSetting(PwmSetting, StoredValue, password.pwm.bean.UserIdentity)} or {@link #writeSetting(PwmSetting, String, StoredValue, password.pwm.bean.UserIdentity)}
+     * for that value so that the xml dom can be updated.
+     * @param storedConfiguration stored configuration to check
+     */
+    private static void checkIfXmlRequiresUpdate(final StoredConfigurationImpl storedConfiguration) throws PwmUnrecoverableException {
+        for (final PwmSetting setting : PwmSetting.values()) {
+            if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
+                final StoredValue value = storedConfiguration.readSetting(setting);
+                if (value.requiresStoredUpdate()) {
+                    storedConfiguration.writeSetting(setting, value, null);
+                }
+            }
+        }
+
+        for (final PwmSettingCategory category : PwmSettingCategory.values()) {
+            if (category.hasProfiles()) {
+                for (final String profileID : storedConfiguration.profilesForSetting(category.getProfileSetting())) {
+                    for (final PwmSetting profileSetting : category.getSettings()) {
+                        final StoredValue value = storedConfiguration.readSetting(profileSetting, profileID);
+                        if (value.requiresStoredUpdate()) {
+                            storedConfiguration.writeSetting(profileSetting, profileID, value, null);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void resetAllPasswordValues(final String comment) {
+        for (final Iterator<SettingValueRecord> settingValueRecordIterator = new StoredValueIterator(false); settingValueRecordIterator.hasNext();) {
+            final SettingValueRecord settingValueRecord = settingValueRecordIterator.next();
+            if (settingValueRecord.getSetting().getSyntax() == PwmSettingSyntax.PASSWORD) {
+                this.resetSetting(settingValueRecord.getSetting(),settingValueRecord.getProfile(),null);
+                if (comment != null && !comment.isEmpty()) {
+                    final XPathExpression xp = XPathBuilder.xpathForSetting(settingValueRecord.getSetting(), settingValueRecord.getProfile());
+                    final Element settingElement = (Element)xp.evaluateFirst(document);
+                    if (settingElement != null) {
+                        settingElement.addContent(new Comment(comment));
+                    }
+                }
+            }
+        }
+    }
+
+    public StoredConfigurationImpl() throws PwmUnrecoverableException {
+        ConfigurationCleaner.cleanup(this);
+        final String createTime = PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date());
+        document.getRootElement().setAttribute(XML_ATTRIBUTE_CREATE_TIME,createTime);
+    }
+
+
+    public String readConfigProperty(final ConfigProperty propertyName) {
+        final XPathExpression xp = XPathBuilder.xpathForConfigProperty(propertyName);
+        final Element propertyElement = (Element)xp.evaluateFirst(document);
+        return propertyElement == null ? null : propertyElement.getText();
+    }
+
+    public void writeConfigProperty(
+            final ConfigProperty propertyName,
+            final String value
+    ) {
+        domModifyLock.writeLock().lock();
+        try {
+
+            final XPathExpression xp = XPathBuilder.xpathForConfigProperty(propertyName);
+            final List<Element> propertyElements = xp.evaluate(document);
+            for (final Element propertyElement : propertyElements) {
+                propertyElement.detach();
+            }
+
+            final Element propertyElement = new Element(XML_ELEMENT_PROPERTY);
+            propertyElement.setAttribute(new Attribute(XML_ATTRIBUTE_KEY,propertyName.getKey()));
+            propertyElement.setContent(new Text(value));
+
+            if (null == XPathBuilder.xpathForConfigProperties().evaluateFirst(document)) {
+                Element configProperties = new Element(XML_ELEMENT_PROPERTIES);
+                configProperties.setAttribute(new Attribute(XML_ATTRIBUTE_TYPE,XML_ATTRIBUTE_VALUE_CONFIG));
+                document.getRootElement().addContent(configProperties);
+            }
+
+            final XPathExpression xp2 = XPathBuilder.xpathForConfigProperties();
+            final Element propertiesElement = (Element)xp2.evaluateFirst(document);
+            propertyElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+            propertiesElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+            propertiesElement.addContent(propertyElement);
+        } finally {
+            domModifyLock.writeLock().unlock();
+        }
+    }
+
+    public void lock() {
+        locked = true;
+    }
+
+    public Map<String,String> readLocaleBundleMap(final String bundleName, final String keyName) {
+        domModifyLock.readLock().lock();
+        try {
+            final XPathExpression xp = XPathBuilder.xpathForLocaleBundleSetting(bundleName, keyName);
+            final Element localeBundleElement = (Element)xp.evaluateFirst(document);
+            if (localeBundleElement != null) {
+                final Map<String,String> bundleMap = new LinkedHashMap<>();
+                for (final Element valueElement : localeBundleElement.getChildren("value")) {
+                    final String localeStrValue = valueElement.getAttributeValue("locale");
+                    bundleMap.put(localeStrValue == null ? "" : localeStrValue, valueElement.getText());
+                }
+                if (!bundleMap.isEmpty()) {
+                    return bundleMap;
+                }
+            }
+        } finally {
+            domModifyLock.readLock().unlock();
+        }
+        return Collections.emptyMap();
+    }
+
+    public Map<String,Object> toOutputMap(final Locale locale) {
+        final List<Map<String,String>> settingData = new ArrayList<>();
+        for (final StoredConfigurationImpl.SettingValueRecord settingValueRecord : this.modifiedSettings()) {
+            final Map<String,String> recordMap = new HashMap<>();
+            recordMap.put("label", settingValueRecord.getSetting().getLabel(locale));
+            if (settingValueRecord.getProfile() != null) {
+                recordMap.put("profile", settingValueRecord.getProfile());
+            }
+            if (settingValueRecord.getStoredValue() != null) {
+                recordMap.put("value", settingValueRecord.getStoredValue().toDebugString(locale));
+            }
+            final SettingMetaData settingMetaData = readSettingMetadata(settingValueRecord.getSetting(), settingValueRecord.getProfile());
+            if (settingMetaData != null) {
+                if (settingMetaData.getModifyDate() != null) {
+                    recordMap.put("modifyTime", PwmConstants.DEFAULT_DATETIME_FORMAT.format(settingMetaData.getModifyDate()));
+                }
+                if (settingMetaData.getUserIdentity() != null) {
+                    recordMap.put("modifyUser",settingMetaData.getUserIdentity().toDisplayString());
+                }
+            }
+            settingData.add(recordMap);
+        }
+
+        final HashMap<String,Object> outputObj = new HashMap<>();
+        outputObj.put("settings", settingData);
+        outputObj.put("template", this.getTemplate().toString());
+
+        return Collections.unmodifiableMap(outputObj);
+    }
+
+    public void resetLocaleBundleMap(final String bundleName, final String keyName) {
+        preModifyActions();
+        domModifyLock.writeLock().lock();
+        try {
+            final XPathExpression xp = XPathBuilder.xpathForLocaleBundleSetting(bundleName, keyName);
+            final List<Element> oldBundleElements = xp.evaluate(document);
+            if (oldBundleElements != null) {
+                for (final Element element : oldBundleElements) {
+                    element.detach();
+                }
+            }
+        } finally {
+            domModifyLock.writeLock().unlock();
+        }
+    }
+
+    public void resetSetting(final PwmSetting setting, final String profileID, final UserIdentity userIdentity) {
+        changeLog.updateChangeLog(setting, profileID, defaultValue(setting, this.getTemplate()));
+        domModifyLock.writeLock().lock();
+        preModifyActions();
+        try {
+            final Element settingElement = createOrGetSettingElement(document, setting, profileID);
+            settingElement.removeContent();
+            settingElement.addContent(new Element(XML_ELEMENT_DEFAULT));
+            updateMetaData(settingElement, userIdentity);
+        } finally {
+            domModifyLock.writeLock().unlock();
+        }
+    }
+
+    public boolean isDefaultValue(final PwmSetting setting) {
+        return isDefaultValue(setting, null);
+    }
+
+    public boolean isDefaultValue(final PwmSetting setting, final String profileID) {
+        domModifyLock.readLock().lock();
+        try {
+            final StoredValue currentValue = readSetting(setting, profileID);
+            if (setting.getSyntax() == PwmSettingSyntax.PASSWORD) {
+                return currentValue == null || currentValue.toNativeObject() == null;
+            }
+            final StoredValue defaultValue = defaultValue(setting, this.getTemplate());
+            final String currentJsonValue = JsonUtil.serialize((Serializable)currentValue.toNativeObject());
+            final String defaultJsonValue = JsonUtil.serialize((Serializable)defaultValue.toNativeObject());
+            return defaultJsonValue.equalsIgnoreCase(currentJsonValue);
+        } finally {
+            domModifyLock.readLock().unlock();
+        }
+    }
+
+    private static StoredValue defaultValue(final PwmSetting pwmSetting, final PwmSettingTemplate template)
+    {
+        try {
+            return pwmSetting.getDefaultValue(template);
+        } catch (PwmException e) {
+            final String errorMsg = "error reading default value for setting " + pwmSetting.toString() + ", error: " + e.getErrorInformation().toDebugStr();
+            LOGGER.error(errorMsg,e);
+            throw new IllegalStateException(errorMsg);
+        }
+    }
+
+    public PwmSettingTemplate getTemplate() {
+        final String propertyValue = readConfigProperty(ConfigProperty.PROPERTY_KEY_TEMPLATE);
+        try {
+            return PwmSettingTemplate.valueOf(propertyValue);
+        } catch (IllegalArgumentException e) {
+            return PwmSettingTemplate.DEFAULT;
+        } catch (NullPointerException e) {
+            return PwmSettingTemplate.DEFAULT;
+        }
+    }
+
+    public void setTemplate(PwmSettingTemplate template) {
+        writeConfigProperty(ConfigProperty.PROPERTY_KEY_TEMPLATE, template.toString());
+    }
+
+    public String toString(final PwmSetting setting, final String profileID ) {
+        final StoredValue storedValue = readSetting(setting, profileID);
+        return setting.getKey() + "=" + storedValue.toDebugString(null);
+    }
+
+    public Map<String,String> getModifiedSettingDebugValues(final Locale locale, final boolean prettyPrint) {
+        final Map<String,String> returnObj = new LinkedHashMap<>();
+        for (SettingValueRecord record : this.modifiedSettings()) {
+            final String label = record.getSetting().toMenuLocationDebug(record.getProfile(),locale);
+            final String value = record.getStoredValue().toDebugString(locale);
+            returnObj.put(label,value);
+        }
+        return returnObj;
+    }
+
+    public List<SettingValueRecord> modifiedSettings() {
+        final List<SettingValueRecord> returnObj = new ArrayList<>();
+        domModifyLock.readLock().lock();
+        try {
+            for (final PwmSetting setting : PwmSetting.values()) {
+                if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
+                    if (!isDefaultValue(setting,null)) {
+                        final StoredValue value = readSetting(setting);
+                        if (value != null) {
+                            returnObj.add(new SettingValueRecord(setting, null, value));
+                        }
+                    }
+                }
+            }
+
+            for (final PwmSettingCategory category : PwmSettingCategory.values()) {
+                if (category.hasProfiles()) {
+                    for (final String profileID : this.profilesForSetting(category.getProfileSetting())) {
+                        for (final PwmSetting profileSetting : category.getSettings()) {
+                            if (!isDefaultValue(profileSetting, profileID)) {
+                                final StoredValue value = readSetting(profileSetting, profileID);
+                                if (value != null) {
+                                    returnObj.add(new SettingValueRecord(profileSetting, profileID, value));
+
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            return returnObj;
+        } finally {
+            domModifyLock.readLock().unlock();
+        }
+    }
+
+    public Serializable toJsonDebugObject() {
+        domModifyLock.readLock().lock();
+        try {
+            final TreeMap<String,Object> outputObject = new TreeMap<>();
+
+            for (final PwmSetting setting : PwmSetting.values()) {
+                if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
+                    if (!isDefaultValue(setting,null)) {
+                        final StoredValue value = readSetting(setting);
+                        outputObject.put(setting.getKey(), value.toDebugJsonObject(null));
+                    }
+                }
+            }
+
+            for (final PwmSettingCategory category : PwmSettingCategory.values()) {
+                if (category.hasProfiles()) {
+                    final TreeMap<String,Object> profiles = new TreeMap<>();
+                    for (final String profileID : this.profilesForSetting(category.getProfileSetting())) {
+                        final TreeMap<String,Object> profileObject = new TreeMap<>();
+                        for (final PwmSetting profileSetting : category.getSettings()) {
+                            if (!isDefaultValue(profileSetting, profileID)) {
+                                final StoredValue value = readSetting(profileSetting, profileID);
+                                profileObject.put(profileSetting.getKey(), value.toDebugJsonObject(null));
+                            }
+                        }
+                        profiles.put(profileID,profileObject);
+                    }
+                    outputObject.put(category.getProfileSetting().getKey(),profiles);
+                }
+            }
+
+            return outputObject;
+        } finally {
+            domModifyLock.readLock().unlock();
+        }
+    }
+
+    public void toXml(final OutputStream outputStream)
+            throws IOException, PwmUnrecoverableException
+    {
+        ConfigurationCleaner.updateMandatoryElements(document);
+        XmlUtil.outputDocument(document, outputStream);
+    }
+
+    public List<String> profilesForSetting(final PwmSetting pwmSetting) {
+        if (!pwmSetting.getCategory().hasProfiles() && pwmSetting.getSyntax() != PwmSettingSyntax.PROFILE) {
+            throw new IllegalArgumentException("cannot build profile list for non-profile setting " + pwmSetting.toString());
+        }
+
+        final PwmSetting profileSetting;
+        if (pwmSetting.getSyntax() == PwmSettingSyntax.PROFILE) {
+            profileSetting = pwmSetting;
+        } else {
+            profileSetting = pwmSetting.getCategory().getProfileSetting();
+        }
+
+        final List<String> settingValues = (List<String>)readSetting(profileSetting).toNativeObject();
+        final LinkedList<String> profiles = new LinkedList<>();
+        profiles.addAll(settingValues);
+        for (Iterator<String> iterator = profiles.iterator(); iterator.hasNext();) {
+            final String profile = iterator.next();
+            if (profile == null || profile.length() < 1) {
+                iterator.remove();
+            }
+        }
+        return Collections.unmodifiableList(profiles);
+    }
+
+    public List<String> validateValues() {
+        final long startTime = System.currentTimeMillis();
+        final List<String> errorStrings = new ArrayList<>();
+
+        for (final PwmSetting loopSetting : PwmSetting.values()) {
+
+            if (loopSetting.getCategory().hasProfiles()) {
+                for (final String profile : profilesForSetting(loopSetting)) {
+                    final String errorAppend = profile;
+                    final StoredValue loopValue = readSetting(loopSetting,profile);
+
+                    try {
+                        final List<String> errors = loopValue.validateValue(loopSetting);
+                        for (final String loopError : errors) {
+                            errorStrings.add(loopSetting.toMenuLocationDebug(profile,PwmConstants.DEFAULT_LOCALE) + errorAppend + " " + loopError);
+                        }
+                    } catch (Exception e) {
+                        LOGGER.error("unexpected error during validate value for " + loopSetting.toMenuLocationDebug(profile,PwmConstants.DEFAULT_LOCALE) + errorAppend + ", error: " + e.getMessage(),e);
+                    }
+                }
+            } else {
+                final StoredValue loopValue = readSetting(loopSetting);
+
+                try {
+                    final List<String> errors = loopValue.validateValue(loopSetting);
+                    for (final String loopError : errors) {
+                        errorStrings.add(loopSetting.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE) + loopError);
+                    }
+                } catch (Exception e) {
+                    LOGGER.error("unexpected error during validate value for " + loopSetting.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE) + ", error: " + e.getMessage(),e);
+                }
+            }
+        }
+
+        LOGGER.trace("StoredConfiguration validator completed in " + TimeDuration.fromCurrent(startTime).asCompactString());
+        return errorStrings;
+    }
+
+    public SettingMetaData readSettingMetadata(final PwmSetting setting, final String profileID) {
+        final XPathExpression xp = XPathBuilder.xpathForSetting(setting, profileID);
+        final Element settingElement = (Element)xp.evaluateFirst(document);
+
+        if (settingElement == null) {
+            return null;
+        }
+
+        final SettingMetaData metaData = new SettingMetaData();
+        try {
+            if (settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME) != null) {
+                metaData.modifyDate = PwmConstants.DEFAULT_DATETIME_FORMAT.parse(
+                        settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME));
+            }
+        } catch (Exception e) {
+            LOGGER.error("can't read modifyDate for setting " + setting.getKey() + ", profile " + profileID + ", error: " + e.getMessage());
+        }
+        try {
+            if (settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_USER) != null) {
+                metaData.userIdentity = UserIdentity.fromDelimitedKey(
+                        settingElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_USER));
+            }
+        } catch (Exception e) {
+            LOGGER.error("can't read userIdentity for setting " + setting.getKey() + ", profile " + profileID + ", error: " + e.getMessage());
+        }
+        return metaData;
+    }
+
+    public List<ConfigRecordID> search(final String searchTerm, final Locale locale) {
+        if (searchTerm == null) {
+            return Collections.emptyList();
+        }
+
+        final LinkedHashSet<ConfigRecordID> returnSet = new LinkedHashSet<>();
+        boolean firstIter = true;
+        for (final String searchWord : StringUtil.whitespaceSplit(searchTerm)) { // split on whitespace
+            final LinkedHashSet<ConfigRecordID> loopResults = new LinkedHashSet<>();
+            for (final PwmSetting loopSetting : PwmSetting.values()) {
+                if (loopSetting.getCategory().hasProfiles()) {
+                    for (final String profile : profilesForSetting(loopSetting)) {
+                        final StoredValue loopValue = readSetting(loopSetting, profile);
+                        if (matchSetting(loopSetting,loopValue,searchWord,locale)) {
+                            loopResults.add(new ConfigRecordID(ConfigRecordID.RecordType.SETTING,loopSetting,profile));
+                        }
+                    }
+                } else {
+                    final StoredValue loopValue = readSetting(loopSetting);
+                    if (matchSetting(loopSetting,loopValue,searchWord,locale)) {
+                        loopResults.add(new ConfigRecordID(ConfigRecordID.RecordType.SETTING,loopSetting,null));
+                    }
+                }
+            }
+            if (firstIter) {
+                returnSet.addAll(loopResults);
+            } else {
+                returnSet.retainAll(loopResults);
+            }
+            firstIter = false;
+        }
+
+        return new ArrayList<>(returnSet);
+    }
+
+    public boolean matchSetting(final PwmSetting setting, final StoredValue value, final String searchTerm, final Locale locale) {
+        if (setting.isHidden() || setting.getCategory().isHidden()) {
+            return false;
+        }
+
+        if (searchTerm == null || searchTerm.isEmpty()) {
+            return false;
+        }
+
+        final String lowerSearchTerm = searchTerm.toLowerCase();
+
+        {
+            final String key = setting.getKey();
+            if (key.toLowerCase().contains(lowerSearchTerm)) {
+                return true;
+            }
+        }
+        {
+            final String label = setting.getLabel(locale);
+            if (label.toLowerCase().contains(lowerSearchTerm)) {
+                return true;
+            }
+        }
+        {
+            final String descr = setting.getDescription(locale);
+            if (descr.toLowerCase().contains(lowerSearchTerm)) {
+                return true;
+            }
+        }
+        {
+            final String menuLocationString = setting.toMenuLocationDebug(null,locale);
+            if (menuLocationString.toLowerCase().contains(lowerSearchTerm)) {
+                return true;
+            }
+        }
+
+        if (setting.isConfidential()) {
+            return false;
+        }
+        {
+            final String valueDebug = value.toDebugString(locale);
+            if (valueDebug != null && valueDebug.toLowerCase().contains(lowerSearchTerm)) {
+                return true;
+            }
+        }
+        if (PwmSettingSyntax.SELECT == setting.getSyntax()
+                || PwmSettingSyntax.OPTIONLIST == setting.getSyntax()
+                || PwmSettingSyntax.VERIFICATION_METHOD == setting.getSyntax()
+                ) {
+            for (final String key : setting.getOptions().keySet()) {
+                if (key.toLowerCase().contains(lowerSearchTerm)) {
+                    return true;
+                }
+                final String optionValue = setting.getOptions().get(key);
+                if (optionValue != null && optionValue.toLowerCase().contains(lowerSearchTerm)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    public StoredValue readSetting(final PwmSetting setting) {
+        return readSetting(setting,null);
+    }
+
+    public StoredValue readSetting(final PwmSetting setting, final String profileID) {
+        if (profileID == null && setting.getCategory().hasProfiles()) {
+            IllegalArgumentException e = new IllegalArgumentException("reading of setting " + setting.getKey() + " requires a non-null profileID");
+            LOGGER.error("error", e);
+            throw e;
+        }
+        if (profileID != null && !setting.getCategory().hasProfiles()) {
+            throw new IllegalStateException("cannot read setting key " + setting.getKey() + " with non-null profileID");
+        }
+        domModifyLock.readLock().lock();
+        try {
+            final XPathExpression xp = XPathBuilder.xpathForSetting(setting, profileID);
+            final Element settingElement = (Element)xp.evaluateFirst(document);
+
+            if (settingElement == null) {
+                return defaultValue(setting, getTemplate());
+            }
+
+            if (settingElement.getChild(XML_ELEMENT_DEFAULT) != null) {
+                return defaultValue(setting, getTemplate());
+            }
+
+            try {
+                return ValueFactory.fromXmlValues(setting, settingElement, getKey());
+            } catch (PwmException e) {
+                final String errorMsg = "unexpected error reading setting '" + setting.getKey() + "' profile '" + profileID + "', error: " + e.getMessage();
+                throw new IllegalStateException(errorMsg);
+            }
+        } finally {
+            domModifyLock.readLock().unlock();
+        }
+    }
+
+    public void writeLocaleBundleMap(final String bundleName, final String keyName, final Map<String,String> localeMap) {
+        ResourceBundle theBundle = null;
+        for (final PwmLocaleBundle bundle : PwmLocaleBundle.values()) {
+            if (bundle.getTheClass().getName().equals(bundleName)) {
+                theBundle = ResourceBundle.getBundle(bundleName);
+            }
+        }
+
+        if (theBundle == null) {
+            LOGGER.info("ignoring unknown locale bundle for bundle=" + bundleName + ", key=" + keyName);
+            return;
+        }
+
+        if (theBundle.getString(keyName) == null) {
+            LOGGER.info("ignoring unknown key for bundle=" + bundleName + ", key=" + keyName);
+            return;
+        }
+
+
+        resetLocaleBundleMap(bundleName, keyName);
+        if (localeMap == null || localeMap.isEmpty()) {
+            LOGGER.info("cleared locale bundle map for bundle=" + bundleName + ", key=" + keyName);
+            return;
+        }
+
+        preModifyActions();
+        changeLog.updateChangeLog(bundleName, keyName, localeMap);
+        try {
+            domModifyLock.writeLock().lock();
+            final Element localeBundleElement = new Element("localeBundle");
+            localeBundleElement.setAttribute("bundle",bundleName);
+            localeBundleElement.setAttribute("key",keyName);
+            for (final String locale : localeMap.keySet()) {
+                final Element valueElement = new Element("value");
+                if (locale != null && locale.length() > 0) {
+                    valueElement.setAttribute("locale",locale);
+                }
+                valueElement.setContent(new CDATA(localeMap.get(locale)));
+                localeBundleElement.addContent(valueElement);
+            }
+            localeBundleElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+            document.getRootElement().addContent(localeBundleElement);
+        } finally {
+            domModifyLock.writeLock().unlock();
+        }
+    }
+
+    public void renameProfileID(final PwmSettingCategory category, final String sourceID, final String destinationID, final UserIdentity userIdentity)
+            throws PwmUnrecoverableException
+    {
+        copyProfileID(category, sourceID, destinationID, userIdentity);
+        if (category == PwmSettingCategory.LDAP_PROFILE) {
+            for (final SettingValueRecord settingValueRecord : this.modifiedSettings()) {
+                if (settingValueRecord.getSetting().getSyntax() == PwmSettingSyntax.USER_PERMISSION) {
+                    UserPermissionValue value = (UserPermissionValue)settingValueRecord.getStoredValue();
+                    final List<UserPermission> newValues = new ArrayList<>();
+                    boolean modified = false;
+
+                    for (final UserPermission userPermission : value.toNativeObject()) {
+                        if (userPermission != null && userPermission.getLdapProfileID() != null) {
+                            if (userPermission.getLdapProfileID().equals(sourceID)) {
+                                final UserPermission newPermission = new UserPermission(
+                                        userPermission.getType(),
+                                        destinationID,
+                                        userPermission.getLdapQuery(),
+                                        userPermission.getLdapBase());
+                                modified = true;
+                                newValues.add(newPermission);
+                            } else {
+                                newValues.add(userPermission);
+                            }
+                        }
+                    }
+
+                    if (modified) {
+                        UserPermissionValue newUserPermissionValue = new UserPermissionValue(newValues);
+                        this.writeSetting(settingValueRecord.getSetting(),settingValueRecord.getProfile(),newUserPermissionValue,userIdentity);
+                    }
+                }
+            }
+        }
+
+        {
+            final List<String> existingProfiles = this.profilesForSetting(category.getProfileSetting());
+            final List<String> newProfileIDList = new ArrayList<>();
+            newProfileIDList.addAll(existingProfiles);
+            newProfileIDList.remove(sourceID);
+            writeSetting(category.getProfileSetting(), new StringArrayValue(newProfileIDList), userIdentity);
+        }
+
+        ConfigurationCleaner.stripOrphanedProfileSettings(this);
+
+    }
+
+    public void copyProfileID(final PwmSettingCategory category, final String sourceID, final String destinationID, final UserIdentity userIdentity)
+            throws PwmUnrecoverableException
+    {
+        if (!category.hasProfiles()) {
+            throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category " + category + ", category does not have profiles");
+        }
+        final List<String> existingProfiles = this.profilesForSetting(category.getProfileSetting());
+        if (!existingProfiles.contains(sourceID)) {
+            throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, source profileID '" + sourceID + "' does not exist");
+        }
+        if (existingProfiles.contains(destinationID)) {
+            throw PwmUnrecoverableException.newException(PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, destination profileID '" + destinationID+ "' already exists");
+        }
+        for (final PwmSetting pwmSetting : category.getSettings()) {
+            if (!isDefaultValue(pwmSetting, sourceID)) {
+                final StoredValue value = readSetting(pwmSetting, sourceID);
+                writeSetting(pwmSetting, destinationID, value, userIdentity);
+            }
+        }
+        final List<String> newProfileIDList = new ArrayList<>();
+        newProfileIDList.addAll(existingProfiles);
+        newProfileIDList.add(destinationID);
+        writeSetting(category.getProfileSetting(), new StringArrayValue(newProfileIDList), userIdentity);
+    }
+
+
+    public void writeSetting(
+            final PwmSetting setting,
+            final StoredValue value,
+            final UserIdentity userIdentity
+    ) throws PwmUnrecoverableException {
+        writeSetting(setting, null, value, userIdentity);
+    }
+
+    public void writeSetting(
+            final PwmSetting setting,
+            final String profileID,
+            final StoredValue value,
+            final UserIdentity userIdentity
+    ) throws PwmUnrecoverableException {
+        if (profileID == null && setting.getCategory().hasProfiles()) {
+            throw new IllegalArgumentException("reading of setting " + setting.getKey() + " requires a non-null profileID");
+        }
+        if (profileID != null && !setting.getCategory().hasProfiles()) {
+            throw new IllegalArgumentException("cannot specify profile for non-profile setting");
+        }
+
+        preModifyActions();
+        changeLog.updateChangeLog(setting, profileID, value);
+        domModifyLock.writeLock().lock();
+        try {
+            final Element settingElement = createOrGetSettingElement(document, setting, profileID);
+            settingElement.removeContent();
+            settingElement.setAttribute(XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString());
+            settingElement.setAttribute(XML_ATTRIBUTE_SYNTAX_VERSION, Integer.toString(value.currentSyntaxVersion()));
+
+            if (setting_writeLabels) {
+                final Element labelElement = new Element("label");
+                labelElement.addContent(setting.getLabel(PwmConstants.DEFAULT_LOCALE));
+                settingElement.addContent(labelElement);
+            }
+
+            if (setting.getSyntax() == PwmSettingSyntax.PASSWORD) {
+                final List<Element> valueElements = ((PasswordValue)value).toXmlValues("value", getKey());
+                settingElement.addContent(new Comment("Note: This value is encrypted and can not be edited directly."));
+                settingElement.addContent(new Comment("Please use the Configuration Manager GUI to modify this value."));
+                settingElement.addContent(valueElements);
+            } else {
+                settingElement.addContent(value.toXmlValues("value"));
+            }
+
+            updateMetaData(settingElement, userIdentity);
+        } finally {
+            domModifyLock.writeLock().unlock();
+        }
+    }
+
+    public String settingChecksum()
+            throws PwmUnrecoverableException
+    {
+        final Date startTime = new Date();
+
+        final List<SettingValueRecord> modifiedSettings = modifiedSettings();
+        final StringBuilder sb = new StringBuilder();
+        sb.append("PwmSettingsChecksum");
+        for (SettingValueRecord settingValueRecord : modifiedSettings) {
+            final StoredValue storedValue = settingValueRecord.getStoredValue();
+            sb.append(storedValue.valueHash());
+        }
+
+
+        final String result = SecureEngine.hash(sb.toString(), PwmConstants.SETTING_CHECKSUM_HASH_METHOD);
+        LOGGER.trace("computed setting checksum in " + TimeDuration.fromCurrent(startTime).asCompactString());
+        return result;
+    }
+
+
+    private void preModifyActions() {
+        if (locked) {
+            throw new UnsupportedOperationException("StoredConfiguration is locked and cannot be modified");
+        }
+        document.getRootElement().setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+    }
+
+// -------------------------- INNER CLASSES --------------------------
+
+    public void setPassword(final String password)
+            throws PwmOperationalException
+    {
+        if (password == null || password.isEmpty()) {
+            throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"can not set blank password"}));
+        }
+        final String trimmedPassword = password.trim();
+        if (trimmedPassword.length() < 1) {
+            throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"can not set blank password"}));
+        }
+
+
+        final String salt = BCrypt.gensalt();
+        final String passwordHash = BCrypt.hashpw(password,salt);
+        this.writeConfigProperty(ConfigProperty.PROPERTY_KEY_PASSWORD_HASH, passwordHash);
+    }
+
+    public boolean verifyPassword(final String password) {
+        if (!hasPassword()) {
+            return false;
+        }
+        final String passwordHash = this.readConfigProperty(ConfigProperty.PROPERTY_KEY_PASSWORD_HASH);
+        return BCrypt.checkpw(password,passwordHash);
+    }
+
+    public boolean hasPassword() {
+        final String passwordHash = this.readConfigProperty(ConfigProperty.PROPERTY_KEY_PASSWORD_HASH);
+        return passwordHash != null && passwordHash.length() > 0;
+    }
+
+    private static abstract class XPathBuilder {
+        private static XPathExpression xpathForLocaleBundleSetting(final String bundleName, final String keyName) {
+            final XPathFactory xpfac = XPathFactory.instance();
+            final String xpathString;
+            xpathString = "//localeBundle[@bundle=\"" + bundleName + "\"][@key=\"" + keyName + "\"]";
+            return xpfac.compile(xpathString);
+        }
+
+        private static XPathExpression xpathForSetting(final PwmSetting setting, final String profileID) {
+            final XPathFactory xpfac = XPathFactory.instance();
+            final String xpathString;
+            if (profileID == null || profileID.length() < 1) {
+                xpathString = "//setting[@key=\"" + setting.getKey() + "\"][(not (@profile)) or @profile=\"\"]";
+            } else {
+                xpathString = "//setting[@key=\"" + setting.getKey() + "\"][@profile=\"" + profileID + "\"]";
+            }
+
+            return xpfac.compile(xpathString);
+        }
+
+        private static XPathExpression xpathForAppProperty(final AppProperty appProperty) {
+            final XPathFactory xpfac = XPathFactory.instance();
+            final String xpathString;
+            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]/"
+                    + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + appProperty.getKey() + "\"]";
+            return xpfac.compile(xpathString);
+        }
+
+        private static XPathExpression xpathForAppProperties() {
+            final XPathFactory xpfac = XPathFactory.instance();
+            final String xpathString;
+            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]";
+            return xpfac.compile(xpathString);
+        }
+
+        private static XPathExpression xpathForConfigProperty(final ConfigProperty configProperty) {
+            final XPathFactory xpfac = XPathFactory.instance();
+            final String xpathString;
+            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]/"
+                    + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + configProperty.getKey() + "\"]";
+            return xpfac.compile(xpathString);
+        }
+
+        private static XPathExpression xpathForConfigProperties() {
+            final XPathFactory xpfac = XPathFactory.instance();
+            final String xpathString;
+            xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]";
+            return xpfac.compile(xpathString);
+        }
+    }
+
+
+    private static class ConfigurationCleaner {
+        private static void cleanup(final StoredConfigurationImpl configuration) throws PwmUnrecoverableException {
+            updateProperitiesWithoutType(configuration);
+            updateMandatoryElements(configuration.document);
+            profilizeNonProfiledSettings(configuration);
+            stripOrphanedProfileSettings(configuration);
+            migrateAppProperties(configuration);
+            updateDeprecatedSettings(configuration);
+        }
+
+        private static void updateMandatoryElements(final Document document) {
+            final Element rootElement = document.getRootElement();
+
+            {
+                final XPathExpression commentXPath = XPathFactory.instance().compile("//comment()[1]");
+                final Comment existingComment = (Comment)commentXPath.evaluateFirst(rootElement);
+                if (existingComment != null) {
+                    existingComment.detach();
+                }
+                final Comment comment = new Comment(generateCommentText());
+                rootElement.addContent(0,comment);
+            }
+
+            rootElement.setAttribute("pwmVersion", PwmConstants.BUILD_VERSION);
+            rootElement.setAttribute("pwmBuild", PwmConstants.BUILD_NUMBER);
+            rootElement.setAttribute("pwmBuildType", PwmConstants.BUILD_TYPE);
+            rootElement.setAttribute("xmlVersion", XML_FORMAT_VERSION);
+
+            { // migrate old properties
+
+                // read correct (new) //properties[@type="config"]
+                final XPathExpression configPropertiesXpath = XPathFactory.instance().compile(
+                        "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]");
+                final Element configPropertiesElement = (Element)configPropertiesXpath.evaluateFirst(rootElement);
+
+                // read list of old //properties[not (@type)]/property
+                final XPathExpression nonAttributedProperty = XPathFactory.instance().compile(
+                        "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]/" + XML_ELEMENT_PROPERTY);
+                final List<Element> nonAttributedProperties = nonAttributedProperty.evaluate(rootElement);
+
+                if (configPropertiesElement != null && nonAttributedProperties != null) {
+                    for (Element element : nonAttributedProperties) {
+                        element.detach();
+                        configPropertiesElement.addContent(element);
+                    }
+                }
+
+                // remove old //properties[not (@type] element
+                final XPathExpression oldPropertiesXpath = XPathFactory.instance().compile(
+                        "//" + XML_ELEMENT_PROPERTIES + "[not (@" + XML_ATTRIBUTE_TYPE + ")]");
+                final List<Element> oldPropertiesElements = oldPropertiesXpath.evaluate(rootElement);
+                if (oldPropertiesElements != null) {
+                    for (Element element : oldPropertiesElements) {
+                        element.detach();
+                    }
+                }
+            }
+        }
+
+        private static String generateCommentText() {
+            final StringBuilder commentText = new StringBuilder();
+            commentText.append("\t\t").append(" ").append("\n");
+            commentText.append("\t\t").append("This configuration file has been auto-generated by the ").append(PwmConstants.PWM_APP_NAME).append(" password self service application.").append("\n");
+            commentText.append("\t\t").append("").append("\n");
+            commentText.append("\t\t").append("WARNING: This configuration file contains sensitive security information, please handle with care!").append("\n");
+            commentText.append("\t\t").append("").append("\n");
+            commentText.append("\t\t").append("WARNING: If a server is currently running using this configuration file, it will be restarted").append("\n");
+            commentText.append("\t\t").append("         and the configuration updated immediately when it is modified.").append("\n");
+            commentText.append("\t\t").append("").append("\n");
+            commentText.append("\t\t").append("NOTICE: This file is encoded as UTF-8.  Do not save or edit this file with an editor that does not").append("\n");
+            commentText.append("\t\t").append("        support UTF-8 encoding.").append("\n");
+            commentText.append("\t\t").append("").append("\n");
+            commentText.append("\t\t").append("If unable to edit using the application ConfigurationEditor web UI, the following options are available.").append("\n");
+            commentText.append("\t\t").append("   or 1. Edit this file directly by hand.").append("\n");
+            commentText.append("\t\t").append("   or 2. Remove restrictions of the configuration by setting the property 'configIsEditable' to 'true' in this file.  This will ").append("\n");
+            commentText.append("\t\t").append("         allow access to the ConfigurationEditor web UI without having to authenticate to an LDAP server first.").append("\n");
+            commentText.append("\t\t").append("   or 3. Remove restrictions of the configuration by using the the command line utility. ").append("\n");
+            commentText.append("\t\t").append("").append("\n");
+            return commentText.toString();
+        }
+
+
+        private static void profilizeNonProfiledSettings(final StoredConfigurationImpl storedConfiguration) throws PwmUnrecoverableException {
+            final String NEW_PROFILE_NAME = "default";
+            final Document document = storedConfiguration.document;
+            for (final PwmSetting setting : PwmSetting.values()) {
+                if (setting.getCategory().hasProfiles()) {
+
+                    final XPathExpression xp = XPathBuilder.xpathForSetting(setting, null);
+                    final Element settingElement = (Element)xp.evaluateFirst(document);
+                    if (settingElement != null) {
+                        LOGGER.info("moving setting " + setting.getKey() + " without profile attribute to profile \"" + NEW_PROFILE_NAME + "\".");
+                        // change setting to "default" profile.
+                        settingElement.setAttribute(XML_ATTRIBUTE_PROFILE, NEW_PROFILE_NAME);
+
+                        final PwmSetting profileSetting = setting.getCategory().getProfileSetting();
+                        final List<String> profileStringDefinitions = new ArrayList<>();
+                        {
+                            final StringArrayValue profileDefinitions = (StringArrayValue) storedConfiguration.readSetting(profileSetting);
+                            if (profileDefinitions != null) {
+                                if (profileDefinitions.toNativeObject() != null) {
+                                    profileStringDefinitions.addAll(profileDefinitions.toNativeObject());
+                                }
+                            }
+                        }
+
+                        if (!profileStringDefinitions.contains(NEW_PROFILE_NAME)) {
+                            profileStringDefinitions.add(NEW_PROFILE_NAME);
+                            storedConfiguration.writeSetting(profileSetting,new StringArrayValue(profileStringDefinitions),null);
+                        }
+                    }
+                }
+            }
+        }
+
+        private static void updateProperitiesWithoutType(final StoredConfigurationImpl storedConfiguration) {
+            final Document document = storedConfiguration.document;
+            final String xpathString = "//properties[not(@type)]";
+            final XPathFactory xpfac = XPathFactory.instance();
+            final XPathExpression xp = xpfac.compile(xpathString);
+            final List<Element> propertiesElements = (List<Element>)xp.evaluate(document);
+            for (final Element propertiesElement : propertiesElements) {
+                propertiesElement.setAttribute(XML_ATTRIBUTE_TYPE,XML_ATTRIBUTE_VALUE_CONFIG);
+            }
+        }
+
+        private static void stripOrphanedProfileSettings(final StoredConfigurationImpl storedConfiguration) {
+            final Document document = storedConfiguration.document;
+            final XPathFactory xpfac = XPathFactory.instance();
+            for (final PwmSetting setting : PwmSetting.values()) {
+                if (setting.getCategory().hasProfiles()) {
+                    final List<String> validProfiles = storedConfiguration.profilesForSetting(setting);
+                    final String xpathString = "//setting[@key=\"" + setting.getKey() + "\"]";
+                    final XPathExpression xp = xpfac.compile(xpathString);
+                    final List<Element> settingElements = (List<Element>)xp.evaluate(document);
+                    for (final Element settingElement : settingElements) {
+                        final String profileID = settingElement.getAttributeValue(XML_ATTRIBUTE_PROFILE);
+                        if (profileID != null) {
+                            if (!validProfiles.contains(profileID)) {
+                                LOGGER.info("removing setting " + setting.getKey() + " with profile \"" + profileID + "\", profile is not a valid profile");
+                                settingElement.detach();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        private static void migrateAppProperties(final StoredConfigurationImpl storedConfiguration) throws PwmUnrecoverableException {
+            final Document document = storedConfiguration.document;
+            final XPathExpression xPathExpression = XPathBuilder.xpathForAppProperties();
+            final List<Element> appPropertiesElements = (List<Element>)xPathExpression.evaluate(document);
+            for (final Element element : appPropertiesElements) {
+                final List<Element> properties = element.getChildren();
+                for (final Element property : properties) {
+                    final String key = property.getAttributeValue("key");
+                    final String value = property.getText();
+                    if (key != null && !key.isEmpty() && value != null && !value.isEmpty()) {
+                        LOGGER.info("migrating app-property config element '" + key + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey());
+                        final String newValue = key + "=" + value;
+                        List<String> existingValues = (List<String>)storedConfiguration.readSetting(PwmSetting.APP_PROPERTY_OVERRIDES).toNativeObject();
+                        if (existingValues == null) {
+                            existingValues = new ArrayList<>();
+                        }
+                        existingValues = new ArrayList<>(existingValues);
+                        existingValues.add(newValue);
+                        storedConfiguration.writeSetting(PwmSetting.APP_PROPERTY_OVERRIDES,new StringArrayValue(existingValues),null);
+                    }
+                }
+                element.detach();
+            }
+        }
+
+        private static void updateDeprecatedSettings(final StoredConfigurationImpl storedConfiguration) throws PwmUnrecoverableException {
+            final UserIdentity actor = new UserIdentity("UpgradeProcessor", null);
+            for (final String profileID : storedConfiguration.profilesForSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY)) {
+                if (!storedConfiguration.isDefaultValue(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID)) {
+                    boolean ad2003Enabled = (boolean) storedConfiguration.readSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY,profileID).toNativeObject();
+                    final StoredValue value;
+                    if (ad2003Enabled) {
+                        value = new StringValue(ADPolicyComplexity.AD2003.toString());
+                    } else {
+                        value = new StringValue(ADPolicyComplexity.NONE.toString());
+                    }
+                    LOGGER.warn("converting deprecated non-default setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
+                            + " to replacement setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + value.toNativeObject().toString());
+                    storedConfiguration.writeSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, actor);
+                    storedConfiguration.resetSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID, actor);
+                }
+            }
+
+            /*
+            {
+                if (!storedConfiguration.isDefaultValue(PwmSetting.CHALLENGE_REQUIRE_RESPONSES)) {
+                    final StoredValue configValue = storedConfiguration.readSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default");
+                    final VerificationMethodValue.VerificationMethodSettings existingSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
+                    final Map<RecoveryVerificationMethod,VerificationMethodValue.VerificationMethodSetting> newMethods = new HashMap<>();
+                    newMethods.putAll(existingSettings.getMethodSettings());
+                    VerificationMethodValue.VerificationMethodSetting setting = new VerificationMethodValue.VerificationMethodSetting(VerificationMethodValue.EnabledState.disabled);
+                    newMethods.put(RecoveryVerificationMethod.CHALLENGE_RESPONSES,setting);
+                    final VerificationMethodValue.VerificationMethodSettings newSettings = new VerificationMethodValue.VerificationMethodSettings(
+                            newMethods,
+                            existingSettings.getMinOptionalRequired()
+                    );
+                    storedConfiguration.writeSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default", new VerificationMethodValue(newSettings), actor);
+                }
+            }
+
+            {
+                if (!storedConfiguration.isDefaultValue(PwmSetting.FORGOTTEN_PASSWORD_REQUIRE_OTP)) {
+                    final StoredValue configValue = storedConfiguration.readSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default");
+                    final VerificationMethodValue.VerificationMethodSettings existingSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
+                    final Map<RecoveryVerificationMethod,VerificationMethodValue.VerificationMethodSetting> newMethods = new HashMap<>();
+                    newMethods.putAll(existingSettings.getMethodSettings());
+                    VerificationMethodValue.VerificationMethodSetting setting = new VerificationMethodValue.VerificationMethodSetting(VerificationMethodValue.EnabledState.required);
+                    newMethods.put(RecoveryVerificationMethod.CHALLENGE_RESPONSES,setting);
+                    final VerificationMethodValue.VerificationMethodSettings newSettings = new VerificationMethodValue.VerificationMethodSettings(
+                            newMethods,
+                            existingSettings.getMinOptionalRequired()
+                    );
+                    storedConfiguration.writeSetting(PwmSetting.FORGOTTEN_PASSWORD_REQUIRE_OTP, "default", new VerificationMethodValue(newSettings), actor);
+                }
+            }
+            */
+        }
+    }
+
+
+    public static class ConfigRecordID implements Serializable {
+        private RecordType recordType;
+        private Object recordID;
+        private String profileID;
+
+        public enum RecordType {
+            SETTING,
+            LOCALE_BUNDLE,
+        }
+
+        public ConfigRecordID(
+                RecordType recordType,
+                Object recordID,
+                String profileID
+        )
+        {
+            this.recordType = recordType;
+            this.recordID = recordID;
+            this.profileID = profileID;
+        }
+
+        @Override
+        public boolean equals(Object o)
+        {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ConfigRecordID that = (ConfigRecordID) o;
+
+            if (profileID != null ? !profileID.equals(that.profileID) : that.profileID != null) return false;
+            if (recordID != null ? !recordID.equals(that.recordID) : that.recordID != null) return false;
+            if (recordType != that.recordType) return false;
+
+            return true;
+        }
+
+        @Override
+        public int hashCode()
+        {
+            int result = recordType != null ? recordType.hashCode() : 0;
+            result = 31 * result + (recordID != null ? recordID.hashCode() : 0);
+            result = 31 * result + (profileID != null ? profileID.hashCode() : 0);
+            return result;
+        }
+
+        public RecordType getRecordType()
+        {
+            return recordType;
+        }
+
+        public Object getRecordID()
+        {
+            return recordID;
+        }
+
+        public String getProfileID()
+        {
+            return profileID;
+        }
+    }
+
+    public String changeLogAsDebugString(final Locale locale, final boolean asHtml) {
+        return changeLog.changeLogAsDebugString(locale, asHtml);
+    }
+
+    private PwmSecurityKey cachedKey = null;
+    public PwmSecurityKey getKey() throws PwmUnrecoverableException {
+        if (cachedKey == null) {
+            cachedKey = new PwmSecurityKey(createTime() + "StoredConfiguration");
+        }
+        return cachedKey;
+    }
+
+    public boolean isModified() {
+        return changeLog.isModified();
+    }
+
+    private class ChangeLog implements Serializable {
+        /* values contain the _original_ toJson version of the value. */
+        private Map<ConfigRecordID,String> changeLog = new LinkedHashMap<>();
+
+        public boolean isModified() {
+            return !changeLog.isEmpty();
+        }
+
+        public String changeLogAsDebugString(final Locale locale, boolean asHtml) {
+            final Map<String,String> outputMap = new TreeMap<>();
+            final String SEPARATOR = LocaleHelper.getLocalizedMessage(locale, Config.Display_SettingNavigationSeparator, null);
+
+            for (final ConfigRecordID configRecordID : changeLog.keySet()) {
+                switch (configRecordID.recordType) {
+                    case SETTING: {
+                        final StoredValue currentValue = readSetting((PwmSetting) configRecordID.recordID, configRecordID.profileID);
+                        final PwmSetting pwmSetting = (PwmSetting) configRecordID.recordID;
+                        final String keyName = pwmSetting.toMenuLocationDebug(configRecordID.getProfileID(), locale);
+                        final String debugValue = currentValue.toDebugString(locale);
+                        outputMap.put(keyName,debugValue);
+                    }
+                    break;
+
+                    case LOCALE_BUNDLE: {
+                        final String key = (String) configRecordID.recordID;
+                        final String bundleName = key.split("!")[0];
+                        final String keys = key.split("!")[1];
+                        final Map<String,String> currentValue = readLocaleBundleMap(bundleName,keys);
+                        final String debugValue = JsonUtil.serializeMap(currentValue, JsonUtil.Flag.PrettyPrint);
+                        outputMap.put("LocaleBundle" + SEPARATOR + bundleName + " " + keys,debugValue);
+                    }
+                    break;
+                }
+            }
+            final StringBuilder output = new StringBuilder();
+            if (outputMap.isEmpty()) {
+                output.append("No setting changes.");
+            } else {
+                for (final String keyName : outputMap.keySet()) {
+                    final String value = outputMap.get(keyName);
+                    if (asHtml) {
+                        output.append("<div class=\"changeLogKey\">");
+                        output.append(keyName);
+                        output.append("</div><div class=\"changeLogValue\">");
+                        output.append(StringUtil.escapeHtml(value));
+                        output.append("</div>");
+                    } else {
+                        output.append(keyName);
+                        output.append("\n");
+                        output.append(" Value: ");
+                        output.append(value);
+                        output.append("\n");
+                    }
+                }
+            }
+            return output.toString();
+        }
+
+        public void updateChangeLog(final String bundleName, final String keyName, final Map<String,String> localeValueMap) {
+            final String key = bundleName + "!" + keyName;
+            final Map<String,String> currentValue = readLocaleBundleMap(bundleName, keyName);
+            final String currentJsonValue = JsonUtil.serializeMap(currentValue);
+            final String newJsonValue = JsonUtil.serializeMap(localeValueMap);
+            final ConfigRecordID configRecordID = new ConfigRecordID(ConfigRecordID.RecordType.LOCALE_BUNDLE, key, null);
+            updateChangeLog(configRecordID,currentJsonValue,newJsonValue);
+        }
+
+        public void updateChangeLog(final PwmSetting setting, final String profileID, final StoredValue newValue) {
+            final StoredValue currentValue = readSetting(setting, profileID);
+            final String currentJsonValue = JsonUtil.serialize(currentValue);
+            final String newJsonValue = JsonUtil.serialize(newValue);
+            final ConfigRecordID configRecordID = new ConfigRecordID(ConfigRecordID.RecordType.SETTING, setting, profileID);
+            updateChangeLog(configRecordID,currentJsonValue,newJsonValue);
+        }
+
+        public void updateChangeLog(final ConfigRecordID configRecordID, final String currentValueString, final String newValueString) {
+            if (changeLog.containsKey(configRecordID)) {
+                final String currentRecord = changeLog.get(configRecordID);
+
+                if (currentRecord == null && newValueString == null) {
+                    changeLog.remove(configRecordID);
+                } else if (currentRecord != null && currentRecord.equals(newValueString)) {
+                    changeLog.remove(configRecordID);
+                }
+            } else {
+                changeLog.put(configRecordID,currentValueString);
+            }
+        }
+    }
+
+    public static void validateXmlSchema(final String xmlDocument)
+            throws PwmUnrecoverableException
+    {
+        return;
+                /*
+        try {
+            final InputStream xsdInputStream = PwmSetting.class.getClassLoader().getResourceAsStream("password/pwm/config/StoredConfiguration.xsd");
+            final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            final Schema schema = factory.newSchema(new StreamSource(xsdInputStream));
+            Validator validator = schema.newValidator();
+            validator.validate(new StreamSource(new StringReader(xmlDocument)));
+        } catch (Exception e) {
+            final String errorMsg = "error while validating setting file schema definition: " + e.getMessage();
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg));
+        }
+        */
+    }
+
+    private static void updateMetaData(final Element settingElement, final UserIdentity userIdentity) {
+        final Element settingsElement = settingElement.getDocument().getRootElement().getChild(XML_ELEMENT_SETTINGS);
+        settingElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+        settingsElement.setAttribute(XML_ATTRIBUTE_MODIFY_TIME,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+        settingElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
+        settingsElement.removeAttribute(XML_ATTRIBUTE_MODIFY_USER);
+        if (userIdentity != null) {
+            settingElement.setAttribute(XML_ATTRIBUTE_MODIFY_USER, userIdentity.toDelimitedKey());
+            settingsElement.setAttribute(XML_ATTRIBUTE_MODIFY_USER, userIdentity.toDelimitedKey());
+        }
+    }
+
+    private static Element createOrGetSettingElement(
+            final Document document,
+            final PwmSetting setting,
+            final String profileID
+    ) {
+        final XPathExpression xp = XPathBuilder.xpathForSetting(setting, profileID);
+        final Element existingSettingElement = (Element)xp.evaluateFirst(document);
+        if (existingSettingElement != null) {
+            return existingSettingElement;
+        }
+
+        final Element settingElement = new Element(XML_ELEMENT_SETTING);
+        settingElement.setAttribute(XML_ATTRIBUTE_KEY, setting.getKey());
+        settingElement.setAttribute(XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString());
+        if (profileID != null && profileID.length() > 0) {
+            settingElement.setAttribute(XML_ATTRIBUTE_PROFILE, profileID);
+        }
+
+        Element settingsElement = document.getRootElement().getChild(XML_ELEMENT_SETTINGS);
+        if (settingsElement == null) {
+            settingsElement = new Element(XML_ELEMENT_SETTINGS);
+            document.getRootElement().addContent(settingsElement);
+        }
+        settingsElement.addContent(settingElement);
+
+        return settingElement;
+    }
+
+    public static class SettingValueRecord implements Serializable {
+        private PwmSetting setting;
+        private String profile;
+        private StoredValue storedValue;
+
+        public SettingValueRecord(
+                PwmSetting setting,
+                String profile,
+                StoredValue storedValue
+        )
+        {
+            this.setting = setting;
+            this.profile = profile;
+            this.storedValue = storedValue;
+        }
+
+        public PwmSetting getSetting()
+        {
+            return setting;
+        }
+
+        public String getProfile()
+        {
+            return profile;
+        }
+
+        public StoredValue getStoredValue()
+        {
+            return storedValue;
+        }
+    }
+
+    class StoredValueIterator implements Iterator<StoredConfigurationImpl.SettingValueRecord> {
+
+        private Queue<SettingValueRecord> settingQueue = new LinkedList<>();
+
+        public StoredValueIterator(boolean includeDefaults) {
+            for (final PwmSetting setting : PwmSetting.values()) {
+                if (setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles()) {
+                    if (includeDefaults || !isDefaultValue(setting)) {
+                        SettingValueRecord settingValueRecord = new SettingValueRecord(setting, null, null);
+                        settingQueue.add(settingValueRecord);
+                    }
+                }
+            }
+
+            for (final PwmSettingCategory category : PwmSettingCategory.values()) {
+                if (category.hasProfiles()) {
+                    for (final String profileID : profilesForSetting(category.getProfileSetting())) {
+                        for (final PwmSetting setting : category.getSettings()) {
+                            if (includeDefaults || !isDefaultValue(setting,profileID)) {
+                                SettingValueRecord settingValueRecord = new SettingValueRecord(setting, profileID, null);
+                                settingQueue.add(settingValueRecord);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+
+        @Override
+        public boolean hasNext()
+        {
+            return !settingQueue.isEmpty();
+        }
+
+        @Override
+        public SettingValueRecord next()
+        {
+            StoredConfigurationImpl.SettingValueRecord settingValueRecord = settingQueue.poll();
+            return new SettingValueRecord(
+                    settingValueRecord.getSetting(),
+                    settingValueRecord.getProfile(),
+                    readSetting(settingValueRecord.getSetting(),settingValueRecord.getProfile())
+            );
+        }
+
+        @Override
+        public void remove()
+        {
+
+        }
+    }
+
+    private String createTime() {
+        final Element rootElement = document.getRootElement();
+        final String createTimeString = rootElement.getAttributeValue(XML_ATTRIBUTE_CREATE_TIME);
+        if (createTimeString == null || createTimeString.isEmpty()) {
+            throw new IllegalStateException("missing createTime timestamp");
+        }
+        return createTimeString;
+    }
+
+    public Date modifyTime() {
+        final Element rootElement = document.getRootElement();
+        final String modifyTimeString = rootElement.getAttributeValue(XML_ATTRIBUTE_MODIFY_TIME);
+        if (modifyTimeString != null) {
+            try {
+                return PwmConstants.DEFAULT_DATETIME_FORMAT.parse(modifyTimeString);
+            } catch (ParseException e) {
+                LOGGER.error("error parsing root last modified timestamp: " + e.getMessage());
+            }
+        }
+        return null;
+    }
+
+    public void initNewRandomSecurityKey()
+            throws PwmUnrecoverableException
+    {
+        if (!isDefaultValue(PwmSetting.PWM_SECURITY_KEY)) {
+            return;
+        }
+
+        writeSetting(
+                PwmSetting.PWM_SECURITY_KEY,
+                new PasswordValue(new PasswordData(PwmRandom.getInstance().alphaNumericString(1024))),
+                null
+        );
+
+        LOGGER.debug("initialized new random security key");
+    }
+
+}

+ 12 - 0
pwm/servlet/src/password/pwm/config/stored/StoredConfigurationProvider.java

@@ -0,0 +1,12 @@
+package password.pwm.config.stored;
+
+import password.pwm.error.PwmUnrecoverableException;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+interface StoredConfigurationProvider {
+    StoredConfiguration fromXml(final InputStream inputStream) throws PwmUnrecoverableException;
+
+    void toXml(final OutputStream outputStream);
+}

+ 2 - 2
pwm/servlet/src/password/pwm/config/value/UserPermissionValue.java

@@ -26,7 +26,7 @@ import com.google.gson.reflect.TypeToken;
 import org.apache.commons.lang3.StringUtils;
 import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.StoredValue;
 import password.pwm.config.UserPermission;
 import password.pwm.error.PwmOperationalException;
@@ -70,7 +70,7 @@ public class UserPermissionValue extends AbstractValue implements StoredValue {
                     throws PwmOperationalException
             {
                 final boolean newType = "2".equals(
-                        settingElement.getAttributeValue(StoredConfiguration.XML_ATTRIBUTE_SYNTAX_VERSION));
+                        settingElement.getAttributeValue(StoredConfigurationImpl.XML_ATTRIBUTE_SYNTAX_VERSION));
                 final List valueElements = settingElement.getChildren("value");
                 final List<UserPermission> values = new ArrayList<>();
                 for (final Object loopValue : valueElements) {

+ 1 - 1
pwm/servlet/src/password/pwm/health/LDAPStatusChecker.java

@@ -114,7 +114,7 @@ public class LDAPStatusChecker implements HealthChecker {
 
         returnRecords.addAll(checkVendorSameness(pwmApplication));
 
-        //returnRecords.addAll(checkUserPermissionValues(pwmApplication));
+        returnRecords.addAll(checkUserPermissionValues(pwmApplication));
 
         return returnRecords;
     }

+ 7 - 7
pwm/servlet/src/password/pwm/http/ContextManager.java

@@ -26,8 +26,8 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
-import password.pwm.config.ConfigurationReader;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
@@ -226,21 +226,21 @@ public class ContextManager implements Serializable {
         }
 
         final String saveConfigOnRestartStrValue = configReader.getStoredConfiguration().readConfigProperty(
-                StoredConfiguration.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START);
+                StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START);
 
         if (saveConfigOnRestartStrValue == null ||  !Boolean.parseBoolean(saveConfigOnRestartStrValue)) {
             return;
         }
 
-        LOGGER.warn("configuration file contains property \"" + StoredConfiguration.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START + "\"=true, will save configuration and set property to false.");
+        LOGGER.warn("configuration file contains property \"" + StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START + "\"=true, will save configuration and set property to false.");
 
         try {
-            final StoredConfiguration newConfig = StoredConfiguration.copy(configReader.getStoredConfiguration());
-            newConfig.writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START, "false");
+            final StoredConfigurationImpl newConfig = StoredConfigurationImpl.copy(configReader.getStoredConfiguration());
+            newConfig.writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START, "false");
             configReader.saveConfiguration(newConfig, pwmApplication, null);
             restartRequestedFlag = true;
         } catch (Exception e) {
-            LOGGER.error("error while saving configuration file commanded by property \"" + StoredConfiguration.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START + "\"=true, error: " + e.getMessage());
+            LOGGER.error("error while saving configuration file commanded by property \"" + StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_SAVE_CONFIG_ON_START + "\"=true, error: " + e.getMessage());
         }
     }
 

+ 7 - 8
pwm/servlet/src/password/pwm/http/PwmRequest.java

@@ -101,7 +101,6 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
             final PwmApplication pwmApplication = ContextManager.getPwmApplication(request);
             pwmRequest = new PwmRequest(request, response, pwmApplication, pwmSession);
             request.setAttribute(PwmConstants.REQUEST_ATTR.PwmRequest.toString(), pwmRequest);
-            checkRequestInstanceNonce(pwmRequest);
         }
         return pwmRequest;
     }
@@ -119,6 +118,7 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         this.pwmSession = pwmSession;
         this.pwmApplication = pwmApplication;
         this.cspNonce = PwmRandom.getInstance().alphaNumericString(10);
+        checkRequestInstanceNonce();
     }
 
     public PwmApplication getPwmApplication()
@@ -213,7 +213,7 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
     public void sendRedirectToContinue()
             throws PwmUnrecoverableException, IOException
     {
-        String redirectURL = this.getContextPath() + PwmServletDefinition.PeopleSearch.servletUrl();
+        String redirectURL = this.getContextPath() + PwmServletDefinition.Command.servletUrl();
         redirectURL = ServletHelper.appendAndEncodeUrlParameters(redirectURL,Collections.singletonMap(PwmConstants.PARAM_ACTION_REQUEST,"continue"));
         sendRedirect(redirectURL);
     }
@@ -597,12 +597,11 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         return ServletHelper.appendAndEncodeUrlParameters(req.getRequestURI(), readParametersAsMap());
     }
 
-    private static void checkRequestInstanceNonce(final PwmRequest pwmRequest) {
-        final String cookieName = pwmRequest.getConfig().readAppProperty(AppProperty.HTTP_COOKIE_INSTANCE_GUID_NAME);
-        final String cookieValue = pwmRequest.readCookie(cookieName);
-        if (cookieValue != null && !cookieValue.equals(pwmRequest.getPwmApplication().getInstanceNonce())) {
-            LOGGER.warn(pwmRequest, "request was generated by client communicating with a foreign server instance");
+    private void checkRequestInstanceNonce() {
+        final String cookieName = getConfig().readAppProperty(AppProperty.HTTP_COOKIE_INSTANCE_GUID_NAME);
+        final String cookieValue = readCookie(cookieName);
+        if (cookieValue != null && !cookieValue.equals(getPwmApplication().getInstanceNonce())) {
+            LOGGER.warn(this, "request was generated by client communicating with a foreign server instance");
         }
-
     }
 }

+ 5 - 1
pwm/servlet/src/password/pwm/http/PwmSession.java

@@ -149,6 +149,10 @@ public class PwmSession implements Serializable {
         return getSessionBean(LoginInfoBean.class);
     }
 
+    public UserSessionDataCacheBean getUserSessionDataCacheBean() {
+        return getSessionBean(UserSessionDataCacheBean.class);
+    }
+
     public UpdateProfileBean getUpdateProfileBean() {
         return getSessionBean(UpdateProfileBean.class);
     }
@@ -190,7 +194,7 @@ public class PwmSession implements Serializable {
         final SessionStateBean ssBean = getSessionStateBean();
 
         if (ssBean.isAuthenticated()) { // try to tear out a session normally.
-            getLoginInfoBean().clearPermissions();
+            getUserSessionDataCacheBean().clearPermissions();
 
             final StringBuilder sb = new StringBuilder();
 

+ 2 - 2
pwm/servlet/src/password/pwm/http/SessionManager.java

@@ -230,7 +230,7 @@ public class SessionManager implements Serializable {
             return false;
         }
 
-        Permission.PERMISSION_STATUS status = pwmSession.getLoginInfoBean().getPermission(permission);
+        Permission.PERMISSION_STATUS status = pwmSession.getUserSessionDataCacheBean().getPermission(permission);
         if (status == Permission.PERMISSION_STATUS.UNCHECKED) {
             if (devDebugMode) {
                 LOGGER.debug(pwmSession.getLabel(), String.format("checking permission %s for user %s", permission.toString(), pwmSession.getUserInfoBean().getUserIdentity().toDelimitedKey()));
@@ -240,7 +240,7 @@ public class SessionManager implements Serializable {
             final List<UserPermission> userPermission = pwmApplication.getConfig().readSettingAsUserPermission(setting);
             final boolean result = LdapPermissionTester.testUserPermissions(pwmApplication, pwmSession.getLabel(), pwmSession.getUserInfoBean().getUserIdentity(), userPermission);
             status = result ? Permission.PERMISSION_STATUS.GRANTED : Permission.PERMISSION_STATUS.DENIED;
-            pwmSession.getLoginInfoBean().setPermission(permission, status);
+            pwmSession.getUserSessionDataCacheBean().setPermission(permission, status);
             LOGGER.debug(pwmSession.getLabel(), String.format("permission %s for user %s is %s",
                     permission.toString(), pwmSession.getUserInfoBean().getUserIdentity().toDelimitedKey(),
                     status.toString()));

+ 0 - 38
pwm/servlet/src/password/pwm/http/bean/AdminBean.java

@@ -1,38 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://code.google.com/p/pwm/
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2015 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.bean;
-
-import password.pwm.http.servlet.AdminServlet;
-
-public class AdminBean implements PwmSessionBean {
-
-    private AdminServlet.Page currentPage = AdminServlet.Page.dashboard;
-
-    public AdminServlet.Page getCurrentPage() {
-        return currentPage;
-    }
-
-    public void setCurrentPage(AdminServlet.Page currentPage) {
-        this.currentPage = currentPage;
-    }
-}

+ 5 - 5
pwm/servlet/src/password/pwm/http/bean/ConfigGuideBean.java

@@ -23,7 +23,7 @@
 package password.pwm.http.bean;
 
 import password.pwm.config.PwmSettingTemplate;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.servlet.ConfigGuideServlet;
 
@@ -34,7 +34,7 @@ import java.util.Map;
 public class ConfigGuideBean implements PwmSessionBean {
 
     private ConfigGuideServlet.STEP step = ConfigGuideServlet.STEP.START;
-    private StoredConfiguration storedConfiguration;
+    private StoredConfigurationImpl storedConfiguration;
     private PwmSettingTemplate selectedTemplate = null;
     private Map<String,String> formData = new HashMap<>();
     private X509Certificate[] ldapCertificates;
@@ -44,7 +44,7 @@ public class ConfigGuideBean implements PwmSessionBean {
 
     public ConfigGuideBean() {
         try {
-            storedConfiguration = StoredConfiguration.newStoredConfiguration();
+            storedConfiguration = StoredConfigurationImpl.newStoredConfiguration();
         } catch (PwmUnrecoverableException e) {
             throw new IllegalStateException(e);
         }
@@ -58,11 +58,11 @@ public class ConfigGuideBean implements PwmSessionBean {
         this.step = step;
     }
 
-    public StoredConfiguration getStoredConfiguration() {
+    public StoredConfigurationImpl getStoredConfiguration() {
         return storedConfiguration;
     }
 
-    public void setStoredConfiguration(StoredConfiguration storedConfiguration) {
+    public void setStoredConfiguration(StoredConfigurationImpl storedConfiguration) {
         this.storedConfiguration = storedConfiguration;
     }
 

+ 4 - 4
pwm/servlet/src/password/pwm/http/bean/ConfigManagerBean.java

@@ -22,10 +22,10 @@
 
 package password.pwm.http.bean;
 
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 
 public class ConfigManagerBean implements PwmSessionBean {
-    private StoredConfiguration storedConfiguration;
+    private StoredConfigurationImpl storedConfiguration;
     private boolean passwordVerified;
     private boolean configUnlockedWarningShown;
 
@@ -34,11 +34,11 @@ public class ConfigManagerBean implements PwmSessionBean {
     public ConfigManagerBean() {
     }
 
-    public StoredConfiguration getStoredConfiguration() {
+    public StoredConfigurationImpl getStoredConfiguration() {
         return storedConfiguration;
     }
 
-    public void setConfiguration(final StoredConfiguration storedConfiguration) {
+    public void setConfiguration(final StoredConfigurationImpl storedConfiguration) {
         this.storedConfiguration = storedConfiguration;
     }
 

+ 4 - 56
pwm/servlet/src/password/pwm/http/bean/LoginInfoBean.java

@@ -22,23 +22,20 @@
 
 package password.pwm.http.bean;
 
-import password.pwm.Permission;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.PasswordData;
-import password.pwm.util.PostChangePasswordAction;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
 
-public class
-        LoginInfoBean implements PwmSessionBean {
+public class LoginInfoBean implements PwmSessionBean {
     private transient PasswordData userCurrentPassword;
 
-    private Map<Permission, Permission.PERMISSION_STATUS> permissions = new HashMap<>();
     private AuthenticationType authenticationType = AuthenticationType.UNAUTHENTICATED;
     private List<AuthenticationType> authenticationFlags = new ArrayList<>();
     private Date localAuthTime;
-    private Map<String, PostChangePasswordAction> postChangePasswordActions = new HashMap<>();
 
     private transient BasicAuthInfo originalBasicAuthInfo;
 
@@ -57,26 +54,6 @@ public class
         this.localAuthTime = localAuthTime;
     }
 
-    public void addPostChangePasswordActions(
-            final String key,
-            final PostChangePasswordAction postChangePasswordAction
-    )
-    {
-        if (postChangePasswordAction == null) {
-            postChangePasswordActions.remove(key);
-        } else {
-            postChangePasswordActions.put(key, postChangePasswordAction);
-        }
-    }
-
-    public List<PostChangePasswordAction> removePostChangePasswordActions()
-    {
-        final List<PostChangePasswordAction> copiedList = new ArrayList<>();
-        copiedList.addAll(postChangePasswordActions.values());
-        postChangePasswordActions.clear();
-        return copiedList;
-    }
-
     public AuthenticationType getAuthenticationType()
     {
         return authenticationType;
@@ -87,35 +64,6 @@ public class
         this.authenticationType = authenticationType;
     }
 
-    public void clearPermissions()
-    {
-        permissions.clear();
-    }
-
-    public Permission.PERMISSION_STATUS getPermission(final Permission permission)
-    {
-        final Permission.PERMISSION_STATUS status = permissions.get(permission);
-        return status == null ? Permission.PERMISSION_STATUS.UNCHECKED : status;
-    }
-
-    public void setPermission(
-            final Permission permission,
-            final Permission.PERMISSION_STATUS status
-    )
-    {
-        permissions.put(permission, status);
-    }
-
-    public Map<Permission, Permission.PERMISSION_STATUS> getPermissions()
-    {
-        return permissions;
-    }
-
-    public void setPermissions(final Map<Permission, Permission.PERMISSION_STATUS> permissions)
-    {
-        this.permissions = permissions;
-    }
-
     public PasswordData getUserCurrentPassword()
     {
         return userCurrentPassword;

+ 64 - 0
pwm/servlet/src/password/pwm/http/bean/UserSessionDataCacheBean.java

@@ -0,0 +1,64 @@
+package password.pwm.http.bean;
+
+import password.pwm.Permission;
+import password.pwm.util.PostChangePasswordAction;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class UserSessionDataCacheBean implements PwmSessionBean {
+    private Map<Permission, Permission.PERMISSION_STATUS> permissions = new HashMap<>();
+    private Map<String, PostChangePasswordAction> postChangePasswordActions = new HashMap<>();
+
+    public void clearPermissions()
+    {
+        permissions.clear();
+    }
+
+    public Permission.PERMISSION_STATUS getPermission(final Permission permission)
+    {
+        final Permission.PERMISSION_STATUS status = permissions.get(permission);
+        return status == null ? Permission.PERMISSION_STATUS.UNCHECKED : status;
+    }
+
+    public void setPermission(
+            final Permission permission,
+            final Permission.PERMISSION_STATUS status
+    )
+    {
+        permissions.put(permission, status);
+    }
+
+    public Map<Permission, Permission.PERMISSION_STATUS> getPermissions()
+    {
+        return permissions;
+    }
+
+    public void setPermissions(final Map<Permission, Permission.PERMISSION_STATUS> permissions)
+    {
+        this.permissions = permissions;
+    }
+
+    public void addPostChangePasswordActions(
+            final String key,
+            final PostChangePasswordAction postChangePasswordAction
+    )
+    {
+        if (postChangePasswordAction == null) {
+            postChangePasswordActions.remove(key);
+        } else {
+            postChangePasswordActions.put(key, postChangePasswordAction);
+        }
+    }
+
+    public List<PostChangePasswordAction> removePostChangePasswordActions()
+    {
+        final List<PostChangePasswordAction> copiedList = new ArrayList<>();
+        copiedList.addAll(postChangePasswordActions.values());
+        postChangePasswordActions.clear();
+        return copiedList;
+    }
+
+}

+ 235 - 0
pwm/servlet/src/password/pwm/http/filter/ConfigAccessFilter.java

@@ -0,0 +1,235 @@
+package password.pwm.http.filter;
+
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ContextManager;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmSession;
+import password.pwm.http.bean.ConfigManagerBean;
+import password.pwm.ldap.auth.AuthenticationType;
+import password.pwm.util.JsonUtil;
+import password.pwm.util.ServletHelper;
+import password.pwm.util.TimeDuration;
+import password.pwm.util.intruder.RecordType;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmHashAlgorithm;
+import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.SecureEngine;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Date;
+
+public class ConfigAccessFilter extends AbstractPwmFilter {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(ConfigAccessFilter.class);
+
+
+    @Override
+    void processFilter(PwmRequest pwmRequest, PwmFilterChain filterChain) throws PwmException, IOException, ServletException {
+        final PwmApplication.MODE appMode = pwmRequest.getPwmApplication().getApplicationMode();
+        if (appMode == PwmApplication.MODE.NEW || appMode == PwmApplication.MODE.CONFIGURATION) {
+            filterChain.doFilter();
+            return;
+        }
+
+        final ConfigManagerBean configManagerBean = pwmRequest.getPwmSession().getConfigManagerBean();
+        if (!checkAuthentication(pwmRequest, configManagerBean)) {
+            filterChain.doFilter();
+        }
+    }
+
+    static boolean checkAuthentication(
+            final PwmRequest pwmRequest,
+            final ConfigManagerBean configManagerBean
+    )
+            throws IOException, PwmUnrecoverableException, ServletException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        final ConfigurationReader runningConfigReader = ContextManager.getContextManager(pwmRequest.getHttpServletRequest().getSession()).getConfigReader();
+        final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration();
+
+        boolean authRequired = false;
+        if (storedConfig.hasPassword()) {
+            authRequired = true;
+        }
+
+        if (PwmApplication.MODE.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) {
+            if (!pwmSession.getSessionStateBean().isAuthenticated()) {
+                throw new PwmUnrecoverableException(PwmError.ERROR_AUTHENTICATION_REQUIRED);
+            }
+
+            if (pwmSession.getLoginInfoBean().getAuthenticationType() != AuthenticationType.AUTHENTICATED) {
+                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_AUTHENTICATION_REQUIRED,
+                        "Username/Password authentication is required to edit configuration.  This session has not been authenticated using a user password (SSO or other method used)."));
+            }
+        }
+
+        if (PwmApplication.MODE.CONFIGURATION != pwmRequest.getPwmApplication().getApplicationMode()) {
+            authRequired = true;
+        }
+
+        if (!authRequired) {
+            return false;
+        }
+
+        if (!storedConfig.hasPassword()) {
+            final String errorMsg = "config file does not have a configuration password";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg,new String[]{errorMsg});
+            pwmRequest.respondWithError(errorInformation, true);
+            return true;
+        }
+
+        if (configManagerBean.isPasswordVerified()) {
+            return false;
+        }
+
+        String persistentLoginValue = null;
+        boolean persistentLoginAccepted = false;
+        boolean persistentLoginEnabled = false;
+        if (pwmRequest.getConfig().isDefaultValue(PwmSetting.PWM_SECURITY_KEY)) {
+            LOGGER.debug(pwmRequest, "security not available, persistent login not possible.");
+        } else {
+            persistentLoginEnabled = true;
+            final PwmSecurityKey securityKey = pwmRequest.getConfig().getSecurityKey();
+
+            if (PwmApplication.MODE.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) {
+                persistentLoginValue = SecureEngine.hash(
+                        storedConfig.readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_PASSWORD_HASH)
+                                + pwmSession.getUserInfoBean().getUserIdentity().toDelimitedKey(),
+                        PwmHashAlgorithm.SHA512);
+
+            } else {
+                persistentLoginValue = SecureEngine.hash(
+                        storedConfig.readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_PASSWORD_HASH),
+                        PwmHashAlgorithm.SHA512);
+            }
+
+            {
+                final String cookieStr = ServletHelper.readCookie(
+                        pwmRequest.getHttpServletRequest(),
+                        PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN
+                );
+                if (securityKey != null && cookieStr != null && !cookieStr.isEmpty()) {
+                    try {
+                        final String jsonStr = pwmApplication.getSecureService().decryptStringValue(cookieStr);
+                        final PersistentLoginInfo persistentLoginInfo = JsonUtil.deserialize(jsonStr, PersistentLoginInfo.class);
+                        if (persistentLoginInfo != null && persistentLoginValue != null) {
+                            if (persistentLoginInfo.getExpireDate().after(new Date())) {
+                                if (persistentLoginValue.equals(persistentLoginInfo.getPassword())) {
+                                    persistentLoginAccepted = true;
+                                    LOGGER.debug(pwmRequest, "accepting persistent config login from cookie (expires "
+                                                    + PwmConstants.DEFAULT_DATETIME_FORMAT.format(persistentLoginInfo.getExpireDate())
+                                                    + ")"
+                                    );
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        LOGGER.error(pwmRequest, "error examining persistent config login cookie: " + e.getMessage());
+                    }
+                    if (!persistentLoginAccepted) {
+                        Cookie removalCookie = new Cookie(PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN, null);
+                        removalCookie.setMaxAge(0);
+                        pwmRequest.getPwmResponse().addCookie(removalCookie);
+                        LOGGER.debug(pwmRequest, "removing non-working persistent config login cookie");
+                    }
+                }
+            }
+        }
+
+
+        final String password = pwmRequest.readParameterAsString("password");
+        boolean passwordAccepted = false;
+        if (!persistentLoginAccepted) {
+            if (password != null && password.length() > 0) {
+                if (storedConfig.verifyPassword(password)) {
+                    passwordAccepted = true;
+                    LOGGER.trace(pwmRequest, "valid configuration password accepted");
+                } else{
+                    LOGGER.trace(pwmRequest, "configuration password is not correct");
+                    pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
+                    pwmApplication.getIntruderManager().mark(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmSession.getLabel());
+                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_WRONGPASSWORD);
+                    pwmRequest.setResponseError(errorInformation);
+                }
+            }
+        }
+
+        final int persistentSeconds = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS));
+        if ((persistentLoginAccepted || passwordAccepted)) {
+            configManagerBean.setPasswordVerified(true);
+            pwmApplication.getIntruderManager().convenience().clearAddressAndSession(pwmSession);
+            pwmApplication.getIntruderManager().clear(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME);
+            if (persistentLoginEnabled && !persistentLoginAccepted && "on".equals(pwmRequest.readParameterAsString("remember"))) {
+                if (persistentSeconds > 0) {
+                    final Date expirationDate = new Date(System.currentTimeMillis() + (persistentSeconds * 1000));
+                    final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo(expirationDate, persistentLoginValue);
+                    final String jsonPersistentLoginInfo = JsonUtil.serialize(persistentLoginInfo);
+                    final String cookieValue = pwmApplication.getSecureService().encryptToString(jsonPersistentLoginInfo);
+                    pwmRequest.getPwmResponse().writeCookie(
+                            PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN,
+                            cookieValue,
+                            persistentSeconds,
+                            true
+                    );
+                    LOGGER.debug(pwmRequest, "set persistent config login cookie (expires "
+                                    + PwmConstants.DEFAULT_DATETIME_FORMAT.format(expirationDate)
+                                    + ")"
+                    );
+                }
+            }
+
+            if (configManagerBean.getPrePasswordEntryUrl() != null) {
+                final String originalUrl = configManagerBean.getPrePasswordEntryUrl();
+                configManagerBean.setPrePasswordEntryUrl(null);
+                pwmRequest.getPwmResponse().sendRedirect(originalUrl);
+                return true;
+            }
+            return false;
+        }
+
+        if (configManagerBean.getPrePasswordEntryUrl() == null) {
+            configManagerBean.setPrePasswordEntryUrl(pwmRequest.getHttpServletRequest().getRequestURL().toString());
+        }
+
+        final String time = new TimeDuration(persistentSeconds * 1000).asLongString(pwmRequest.getLocale());
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigPasswordRememberTime,time);
+        pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_LOGIN);
+        return true;
+    }
+
+    private static class PersistentLoginInfo implements Serializable {
+        private Date expireDate;
+        private String password;
+
+        private PersistentLoginInfo(
+                Date expireDate,
+                String password
+        )
+        {
+            this.expireDate = expireDate;
+            this.password = password;
+        }
+
+        public Date getExpireDate()
+        {
+            return expireDate;
+        }
+
+        public String getPassword()
+        {
+            return password;
+        }
+    }
+}

+ 7 - 4
pwm/servlet/src/password/pwm/http/filter/SessionFilter.java

@@ -37,10 +37,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmResponse;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
-import password.pwm.util.Helper;
-import password.pwm.util.ServletHelper;
-import password.pwm.util.StringUtil;
-import password.pwm.util.TimeDuration;
+import password.pwm.util.*;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.stats.Statistic;
 
@@ -101,6 +98,12 @@ public class SessionFilter extends AbstractPwmFilter {
 
             throw new ServletException(e);
         }
+
+        System.out.println(
+                pwmRequest.getPwmApplication().getSecureService().encryptToString(
+                        JsonUtil.serialize(pwmRequest.getPwmSession().getLoginInfoBean())
+                )
+        );
     }
 
     private boolean handleStandardRequestOperations(

+ 40 - 0
pwm/servlet/src/password/pwm/http/servlet/AccountInformationServlet.java

@@ -0,0 +1,40 @@
+package password.pwm.http.servlet;
+
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import password.pwm.PwmConstants;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import java.io.IOException;
+
+@WebServlet(
+        name="UserInformationServlet",
+        urlPatterns = {
+                PwmConstants.URL_PREFIX_PRIVATE + "/account",
+                PwmConstants.URL_PREFIX_PRIVATE + "/userinfo",
+                PwmConstants.URL_PREFIX_PRIVATE + "/userinfo.jsp"
+        }
+)
+public class AccountInformationServlet extends AbstractPwmServlet {
+    @Override
+    protected void processAction(PwmRequest pwmRequest) throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException {
+
+        if (!pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.ACCOUNT_INFORMATION_ENABLED)) {
+            pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE));
+            return;
+        }
+
+
+        pwmRequest.forwardToJsp(PwmConstants.JSP_URL.ACCOUNT_INFORMATION);
+    }
+
+    @Override
+    protected ProcessAction readProcessAction(PwmRequest request) throws PwmUnrecoverableException {
+        return null;
+    }
+}

+ 2 - 2
pwm/servlet/src/password/pwm/http/servlet/ActivateUserServlet.java

@@ -46,9 +46,9 @@ import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;
-import password.pwm.token.TokenType;
 import password.pwm.token.TokenPayload;
 import password.pwm.token.TokenService;
+import password.pwm.token.TokenType;
 import password.pwm.util.PostChangePasswordAction;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -416,7 +416,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
                 }
             };
 
-            pwmSession.getLoginInfoBean().addPostChangePasswordActions("activateUserWriteAttributes", postAction);
+            pwmSession.getUserSessionDataCacheBean().addPostChangePasswordActions("activateUserWriteAttributes", postAction);
         } catch (ImpossiblePasswordPolicyException e) {
             final ErrorInformation info = new ErrorInformation(PwmError.ERROR_UNKNOWN, "unexpected ImpossiblePasswordPolicyException error while activating user");
             LOGGER.warn(pwmSession, info, e);

+ 41 - 36
pwm/servlet/src/password/pwm/http/servlet/AdminServlet.java

@@ -31,7 +31,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.bean.AdminBean;
+import password.pwm.http.PwmURL;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.report.ReportService;
 import password.pwm.util.stats.StatisticsManager;
@@ -47,8 +47,9 @@ import java.util.Collections;
 @WebServlet(
         name = "AdminServlet",
         urlPatterns = {
-            PwmConstants.URL_PREFIX_PRIVATE + "/admin/administration",
-            PwmConstants.URL_PREFIX_PRIVATE + "/admin/Administration",
+                PwmConstants.URL_PREFIX_PRIVATE + "/admin",
+                PwmConstants.URL_PREFIX_PRIVATE + "/admin/*",
+                PwmConstants.URL_PREFIX_PRIVATE + "/admin/Administration",
         }
 )
 public class AdminServlet extends AbstractPwmServlet {
@@ -56,7 +57,6 @@ public class AdminServlet extends AbstractPwmServlet {
     private static final PwmLogger LOGGER = PwmLogger.forClass(AdminServlet.class);
 
     public enum AdminAction implements AbstractPwmServlet.ProcessAction {
-        changePage(HttpMethod.POST),
         viewLogWindow(HttpMethod.GET),
         downloadAuditLogCsv(HttpMethod.POST),
         downloadUserReportCsv(HttpMethod.POST),
@@ -102,22 +102,17 @@ public class AdminServlet extends AbstractPwmServlet {
             return;
         }
 
-        final AdminBean adminBean = pwmRequest.getPwmSession().getSessionBean(AdminBean.class);
         final AdminAction action = readProcessAction(pwmRequest);
         if (action != null) {
             switch(action) {
-                case changePage:
-                    handleChangePageRequest(pwmRequest, adminBean);
-                    break;
-
                 case viewLogWindow:
                     processViewLog(pwmRequest);
                     return;
-                
+
                 case downloadAuditLogCsv:
                     downloadAuditLogCsv(pwmRequest);
                     return;
-                
+
                 case downloadUserReportCsv:
                     downloadUserReportCsv(pwmRequest);
                     return;
@@ -132,22 +127,7 @@ public class AdminServlet extends AbstractPwmServlet {
             }
         }
 
-        pwmRequest.forwardToJsp(adminBean.getCurrentPage().getJspURL());
-    }
-
-    private void handleChangePageRequest(final PwmRequest pwmRequest, final AdminBean adminBean)
-            throws PwmUnrecoverableException
-    {
-        final String requestedPage = pwmRequest.readParameterAsString("page");
-        if (requestedPage != null) {
-            try {
-                adminBean.setCurrentPage(Page.valueOf(requestedPage));
-            } catch (IllegalArgumentException e) {
-                LOGGER.error(pwmRequest, "request to change page to unknown page name: " + requestedPage);
-            }
-        } else {
-            LOGGER.error(pwmRequest, "request to change page to but no page parameter: ");
-        }
+        forwardToJsp(pwmRequest);
     }
 
     private void processViewLog(
@@ -239,25 +219,50 @@ public class AdminServlet extends AbstractPwmServlet {
         }
     }
 
+    public void forwardToJsp(final PwmRequest pwmRequest) throws ServletException, PwmUnrecoverableException, IOException {
+        final Page currentPage = Page.forUrl(pwmRequest.getURL());
+        if (currentPage != null) {
+            pwmRequest.forwardToJsp(currentPage.getJspURL());
+            return;
+        }
+        pwmRequest.sendRedirect(pwmRequest.getContextPath() + PwmServletDefinition.Admin.servletUrl() + Page.dashboard.getUrlSuffix());
+    }
+
 
     public enum Page {
-        dashboard(PwmConstants.JSP_URL.ADMIN_DASHBOARD),
-        analysis(PwmConstants.JSP_URL.ADMIN_ANALYSIS),
-        activity(PwmConstants.JSP_URL.ADMIN_ACTIVITY),
-        tokenlookup(PwmConstants.JSP_URL.ADMIN_TOKEN_LOOKUP),
-        viewLog(PwmConstants.JSP_URL.ADMIN_LOGVIEW),
-        urlReference(PwmConstants.JSP_URL.ADMIN_URLREFERENCE),
-        
+        dashboard(PwmConstants.JSP_URL.ADMIN_DASHBOARD,"/dashboard"),
+        analysis(PwmConstants.JSP_URL.ADMIN_ANALYSIS,"/analysis"),
+        activity(PwmConstants.JSP_URL.ADMIN_ACTIVITY,"/activity"),
+        tokenLookup(PwmConstants.JSP_URL.ADMIN_TOKEN_LOOKUP,"/tokens"),
+        viewLog(PwmConstants.JSP_URL.ADMIN_LOGVIEW,"/logs"),
+        urlReference(PwmConstants.JSP_URL.ADMIN_URLREFERENCE,"/urls"),
+
         ;
-        
+
         private final PwmConstants.JSP_URL jspURL;
+        private final String urlSuffix;
 
-        Page(PwmConstants.JSP_URL jspURL) {
+        Page(PwmConstants.JSP_URL jspURL, String urlSuffix) {
             this.jspURL = jspURL;
+            this.urlSuffix = urlSuffix;
         }
 
         public PwmConstants.JSP_URL getJspURL() {
             return jspURL;
         }
+
+        public String getUrlSuffix() {
+            return urlSuffix;
+        }
+
+        public static Page forUrl(final PwmURL pwmURL) {
+            final String url = pwmURL.toString();
+            for (final Page page : Page.values()) {
+                if (url.endsWith(page.urlSuffix)) {
+                    return page;
+                }
+            }
+            return null;
+        }
     }
 }

+ 23 - 19
pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java

@@ -30,6 +30,7 @@ import password.pwm.Validator;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.*;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.config.value.X509CertificateValue;
@@ -40,6 +41,7 @@ import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ConfigManagerBean;
+import password.pwm.http.servlet.configmanager.ConfigManagerServlet;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.LocaleHelper;
 import password.pwm.i18n.Message;
@@ -63,6 +65,10 @@ import java.util.*;
 @WebServlet(
         name = "ConfigEditorServlet",
         urlPatterns = {
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/editor",
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/editor/*",
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/configeditor",
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/configeditor/*",
                 PwmConstants.URL_PREFIX_PRIVATE + "/config/ConfigEditor",
                 PwmConstants.URL_PREFIX_PRIVATE + "/config/ConfigEditor/*",
         }
@@ -159,10 +165,8 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final ConfigManagerBean configManagerBean = pwmSession.getConfigManagerBean();
 
-        ConfigManagerServlet.checkAuthentication(pwmRequest, configManagerBean);
-
         if (configManagerBean.getStoredConfiguration() == null) {
-            final StoredConfiguration loadedConfig = ConfigManagerServlet.readCurrentConfiguration(pwmRequest);
+            final StoredConfigurationImpl loadedConfig = ConfigManagerServlet.readCurrentConfiguration(pwmRequest);
             configManagerBean.setConfiguration(loadedConfig);
         }
 
@@ -299,7 +303,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
     )
             throws IOException, PwmUnrecoverableException
     {
-        final StoredConfiguration storedConfig = configManagerBean.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfig = configManagerBean.getStoredConfiguration();
 
         final String key = pwmRequest.readParameterAsString("key");
         final Object returnValue;
@@ -364,7 +368,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
                 returnMap.put("options", theSetting.getOptions());
             }
             {
-                final StoredConfiguration.SettingMetaData settingMetaData = storedConfig.readSettingMetadata(theSetting, profile);
+                final StoredConfigurationImpl.SettingMetaData settingMetaData = storedConfig.readSettingMetadata(theSetting, profile);
                 if (settingMetaData != null) {
                     if (settingMetaData.getModifyDate() != null) {
                         returnMap.put("modifyTime", settingMetaData.getModifyDate());
@@ -387,7 +391,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
             final ConfigManagerBean configManagerBean
     )
             throws IOException, PwmUnrecoverableException {
-        final StoredConfiguration storedConfig = configManagerBean.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfig = configManagerBean.getStoredConfiguration();
         final String key = pwmRequest.readParameterAsString("key");
         final String bodyString = pwmRequest.readRequestBodyAsString();
         final PwmSetting setting = PwmSetting.forKey(key);
@@ -434,7 +438,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
             final ConfigManagerBean configManagerBean
     )
             throws IOException, PwmUnrecoverableException {
-        final StoredConfiguration storedConfig = configManagerBean.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfig = configManagerBean.getStoredConfiguration();
         final UserIdentity loggedInUser = pwmRequest.getUserInfoIfLoggedIn();
         final String key = pwmRequest.readParameterAsString("key");
         final PwmSetting setting = PwmSetting.forKey(key);
@@ -524,7 +528,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
                 try {
                     final String bodyString = pwmRequest.readRequestBodyAsString();
                     final String value = JsonUtil.deserialize(bodyString, String.class);
-                    configManagerBean.getStoredConfiguration().writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_NOTES,
+                    configManagerBean.getStoredConfiguration().writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_NOTES,
                             value);
                     LOGGER.trace("updated notesText");
                 } catch (Exception e) {
@@ -537,11 +541,11 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
                     try {
                         final PwmSettingTemplate template = PwmSettingTemplate.valueOf(requestedTemplate);
                         configManagerBean.getStoredConfiguration().writeConfigProperty(
-                                StoredConfiguration.ConfigProperty.PROPERTY_KEY_TEMPLATE, template.toString());
+                                StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_TEMPLATE, template.toString());
                         LOGGER.trace("setting template to: " + requestedTemplate);
                     } catch (IllegalArgumentException e) {
                         configManagerBean.getStoredConfiguration().writeConfigProperty(
-                                StoredConfiguration.ConfigProperty.PROPERTY_KEY_TEMPLATE, PwmSettingTemplate.DEFAULT.toString());
+                                StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_TEMPLATE, PwmSettingTemplate.DEFAULT.toString());
                         LOGGER.error("unknown template set request: " + requestedTemplate);
                     }
                 }
@@ -593,11 +597,11 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
         final RestResultBean restResultBean = new RestResultBean();
         final String searchTerm = valueMap.get("search");
         if (searchTerm != null && !searchTerm.isEmpty()) {
-            final ArrayList<StoredConfiguration.ConfigRecordID> searchResults = new ArrayList<>(configManagerBean.getStoredConfiguration().search(searchTerm, locale));
+            final ArrayList<StoredConfigurationImpl.ConfigRecordID> searchResults = new ArrayList<>(configManagerBean.getStoredConfiguration().search(searchTerm, locale));
             final TreeMap<String, Map<String, Map<String, Object>>> returnData = new TreeMap<>();
 
-            for (final StoredConfiguration.ConfigRecordID recordID : searchResults) {
-                if (recordID.getRecordType() == StoredConfiguration.ConfigRecordID.RecordType.SETTING) {
+            for (final StoredConfigurationImpl.ConfigRecordID recordID : searchResults) {
+                if (recordID.getRecordType() == StoredConfigurationImpl.ConfigRecordID.RecordType.SETTING) {
                     final PwmSetting setting = (PwmSetting) recordID.getRecordID();
                     final LinkedHashMap<String, Object> settingData = new LinkedHashMap<>();
                     settingData.put("category", setting.getCategory().toString());
@@ -618,7 +622,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
             restResultBean.setData(returnData);
             LOGGER.trace(pwmRequest, "finished search operation with " + returnData.size() + " results in " + TimeDuration.fromCurrent(startTime).asCompactString());
         } else {
-            restResultBean.setData(new ArrayList<StoredConfiguration.ConfigRecordID>());
+            restResultBean.setData(new ArrayList<StoredConfigurationImpl.ConfigRecordID>());
         }
 
         pwmRequest.outputJsonResult(restResultBean);
@@ -765,7 +769,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
             navigationData.add(categoryInfo);
         }
 
-        final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfiguration = configManagerBean.getStoredConfiguration();
         for (final PwmSettingCategory loopCategory : PwmSettingCategory.sortedValues(pwmRequest.getLocale())) {
             if (NavTreeHelper.categoryMatcher(loopCategory, storedConfiguration, modifiedSettingsOnly, (int)level, filterText)) {
                 final Map<String, Object> categoryInfo = new LinkedHashMap<>();
@@ -855,7 +859,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
         private static Set<String> determineModifiedKeysSettings(
                 final PwmLocaleBundle bundle,
                 final Configuration config,
-                final StoredConfiguration storedConfiguration
+                final StoredConfigurationImpl storedConfiguration
         ) {
             final Set<String> modifiedKeys = new TreeSet<>();
             for (final String key : bundle.getKeys()) {
@@ -878,7 +882,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
 
         private static boolean categoryMatcher(
                 PwmSettingCategory category,
-                StoredConfiguration storedConfiguration,
+                StoredConfigurationImpl storedConfiguration,
                 final boolean modifiedOnly,
                 final int minLevel,
                 final String text
@@ -912,7 +916,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
         }
 
         private static boolean settingMatches(
-                final StoredConfiguration storedConfiguration,
+                final StoredConfigurationImpl storedConfiguration,
                 final PwmSetting setting,
                 final String profileID,
                 final boolean modifiedOnly,
@@ -1020,7 +1024,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
                     configManagerBean.setConfigUnlockedWarningShown(true);
                 }
             }
-            varMap.put("configurationNotes", configManagerBean.getStoredConfiguration().readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_NOTES));
+            varMap.put("configurationNotes", configManagerBean.getStoredConfiguration().readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_NOTES));
             returnMap.put("var", varMap);
         }
 

+ 24 - 27
pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java

@@ -35,6 +35,8 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.*;
 import password.pwm.config.function.UserMatchViewerFunction;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.value.*;
 import password.pwm.error.*;
 import password.pwm.health.*;
@@ -205,7 +207,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         final ConfigGuideBean configGuideBean = pwmSession.getSessionBean(ConfigGuideBean.class);
 
         if (pwmApplication.getApplicationMode() != PwmApplication.MODE.NEW) {
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"ConfigGuide unavailable unless in NEW mode");
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,"ConfigGuide unavailable unless in NEW mode");
             LOGGER.error(pwmSession, errorInformation.toDebugStr());
             pwmRequest.respondWithError(errorInformation);
             return;
@@ -275,7 +277,9 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             }
         }
 
-        forwardToJSP(pwmRequest);
+        if (!pwmRequest.getPwmResponse().getHttpServletResponse().isCommitted()) {
+            forwardToJSP(pwmRequest);
+        }
     }
 
     public static void restUploadConfig(final PwmRequest pwmRequest)
@@ -296,7 +300,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             final InputStream uploadedFile = ServletHelper.readFileUpload(req,"uploadFile");
             if (uploadedFile != null) {
                 try {
-                    final StoredConfiguration storedConfig = StoredConfiguration.fromXml(uploadedFile);
+                    final StoredConfigurationImpl storedConfig = StoredConfigurationImpl.fromXml(uploadedFile);
                     final List<String> configErrors = storedConfig.validateValues();
                     if (configErrors != null && !configErrors.isEmpty()) {
                         throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,configErrors.get(0)));
@@ -450,7 +454,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             final PwmRequest pwmRequest,
             final ConfigGuideBean configGuideBean
     ) throws IOException, ServletException, PwmUnrecoverableException {
-        final StoredConfiguration storedConfiguration = StoredConfiguration.copy(configGuideBean.getStoredConfiguration());
+        final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.copy(configGuideBean.getStoredConfiguration());
         if (configGuideBean.getStep() == STEP.LDAP_ADMIN) {
             storedConfiguration.resetSetting(PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_KEY, null);
             storedConfiguration.resetSetting(PwmSetting.LDAP_PROXY_USER_PASSWORD, LDAP_PROFILE_KEY, null);
@@ -478,7 +482,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
     )
             throws IOException, PwmUnrecoverableException
     {
-        final StoredConfiguration storedConfiguration = configGuideBean.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfiguration = configGuideBean.getStoredConfiguration();
         final Map<String,String> incomingFormData = pwmRequest.readBodyAsJsonStringMap();
 
         if (incomingFormData != null) {
@@ -494,13 +498,6 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
                     configGuideBean.getFormData().putAll(defaultForm);
                     configGuideBean.setSelectedTemplate(template);
                     storedConfiguration.setTemplate(template);
-                    {
-                        final String settingValue = AppProperty.LDAP_PROMISCUOUS_ENABLE.getKey() + "=true";
-                        storedConfiguration.writeSetting(
-                                PwmSetting.APP_PROPERTY_OVERRIDES,
-                                new StringArrayValue(Collections.singletonList(settingValue)),
-                                null);
-                    }
                 }
             } catch (Exception e) {
                 LOGGER.error("unknown template set request: " + e.getMessage());
@@ -575,7 +572,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
     }
 
     public static void convertFormToConfiguration(
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationImpl storedConfiguration,
             final Map<String, String> ldapForm,
             final Map<String, String> incomingLdapForm
     )
@@ -633,12 +630,12 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             final ContextManager contextManager,
             final ConfigGuideBean configGuideBean
     ) throws PwmOperationalException, PwmUnrecoverableException {
-        final StoredConfiguration storedConfiguration = configGuideBean.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfiguration = configGuideBean.getStoredConfiguration();
         final String configPassword = configGuideBean.getFormData().get(PARAM_CONFIG_PASSWORD);
         if (configPassword != null && configPassword.length() > 0) {
             storedConfiguration.setPassword(configPassword);
         } else {
-            storedConfiguration.writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_PASSWORD_HASH, null);
+            storedConfiguration.writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_PASSWORD_HASH, null);
         }
 
         { // determine Cr Preference setting.
@@ -649,13 +646,12 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             }
         }
 
-        storedConfiguration.readSetting(PwmSetting.APP_PROPERTY_OVERRIDES);
         writeConfig(contextManager, storedConfiguration);
     }
 
     private static void writeConfig(
             final ContextManager contextManager,
-            final StoredConfiguration storedConfiguration
+            final StoredConfigurationImpl storedConfiguration
     ) throws PwmOperationalException, PwmUnrecoverableException {
         ConfigurationReader configReader = contextManager.getConfigReader();
         PwmApplication pwmApplication = contextManager.getPwmApplication();
@@ -664,7 +660,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             // add a random security key
             storedConfiguration.initNewRandomSecurityKey();
 
-            storedConfiguration.writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE, "true");
+            storedConfiguration.writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE, "true");
             configReader.saveConfiguration(storedConfiguration, pwmApplication, null);
 
             contextManager.requestPwmApplicationRestart();
@@ -735,17 +731,18 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         final Map<String,String> formData = configGuideBean.getFormData();
         final String host = formData.get(PARAM_LDAP_HOST);
         final int port = Integer.parseInt(formData.get(PARAM_LDAP_PORT));
-        if (Boolean.parseBoolean(formData.get(PARAM_LDAP_SECURE))) {
-            X509Utils.readRemoteCertificates(host,port);
-        } else {
-            InetAddress addr = InetAddress.getByName(host);
-            SocketAddress sockaddr = new InetSocketAddress(addr, port);
-            Socket sock = new Socket();
 
-            // this method will block for the defined number of milliseconds
-            int timeout = 2000;
-            sock.connect(sockaddr, timeout);
+        { // socket test
+            final InetAddress inetAddress = InetAddress.getByName(host);
+            final SocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
+            final Socket socket = new Socket();
+
+            final int timeout = 2000;
+            socket.connect(socketAddress, timeout);
         }
 
+        if (Boolean.parseBoolean(formData.get(PARAM_LDAP_SECURE))) {
+            X509Utils.readRemoteCertificates(host, port);
+        }
     }
 }

+ 1 - 1
pwm/servlet/src/password/pwm/http/servlet/NewUserServlet.java

@@ -82,7 +82,7 @@ import java.util.*;
                 PwmConstants.URL_PREFIX_PUBLIC + "/newuser",
                 PwmConstants.URL_PREFIX_PUBLIC + "/newuser/*",
                 PwmConstants.URL_PREFIX_PUBLIC + "/NewUser",
-                PwmConstants.URL_PREFIX_PUBLIC + "/NewUser/*"
+                PwmConstants.URL_PREFIX_PUBLIC + "/NewUser/*",
         }
 )
 public class NewUserServlet extends AbstractPwmServlet {

+ 5 - 1
pwm/servlet/src/password/pwm/http/servlet/PwmServletDefinition.java

@@ -25,6 +25,8 @@ package password.pwm.http.servlet;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.servlet.configmanager.ConfigManagerServlet;
+import password.pwm.http.servlet.configmanager.ConfigManagerWordlistServlet;
 
 import javax.servlet.annotation.WebServlet;
 import java.lang.annotation.Annotation;
@@ -37,6 +39,7 @@ public enum PwmServletDefinition {
     Command(password.pwm.http.servlet.CommandServlet.class),
     //Resource(password.pwm.http.servlet.ResourceFileServlet.class),
 
+    AccountInformation(AccountInformationServlet.class),
     ChangePassword(password.pwm.http.servlet.ChangePasswordServlet.class),
     SetupResponses(password.pwm.http.servlet.SetupResponsesServlet.class),
     UpdateProfile(password.pwm.http.servlet.UpdateProfileServlet.class),
@@ -48,7 +51,8 @@ public enum PwmServletDefinition {
     Admin(password.pwm.http.servlet.AdminServlet.class),
     ConfigGuide(password.pwm.http.servlet.ConfigGuideServlet.class),
     ConfigEditor(password.pwm.http.servlet.ConfigEditorServlet.class),
-    ConfigManager(password.pwm.http.servlet.ConfigManagerServlet.class),
+    ConfigManager(ConfigManagerServlet.class),
+    ConfigManager_Wordlists(ConfigManagerWordlistServlet.class),
 
     NewUser(password.pwm.http.servlet.NewUserServlet.class),
     ActivateUser(password.pwm.http.servlet.ActivateUserServlet.class),

+ 14 - 2
pwm/servlet/src/password/pwm/http/servlet/SetupOtpServlet.java

@@ -31,6 +31,8 @@ import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.ForceSetupPolicy;
 import password.pwm.error.*;
+import password.pwm.event.AuditEvent;
+import password.pwm.event.UserAuditRecord;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
@@ -61,8 +63,8 @@ import java.util.Map;
 @WebServlet(
         name="SetupOtpServlet",
         urlPatterns={
-                PwmConstants.URL_PREFIX_PRIVATE + "setupotp",
-                PwmConstants.URL_PREFIX_PRIVATE + "SetupOtp"
+                PwmConstants.URL_PREFIX_PRIVATE + "/setup-otp",
+                PwmConstants.URL_PREFIX_PRIVATE + "/SetupOtp"
         }
 )
 public class SetupOtpServlet extends AbstractPwmServlet {
@@ -206,6 +208,16 @@ public class SetupOtpServlet extends AbstractPwmServlet {
                         otpBean.getOtpUserRecord()
                 );
                 otpBean.setWritten(true);
+
+                // mark the event log
+                final UserAuditRecord auditRecord = pwmApplication.getAuditManager().createUserAuditRecord(
+                        AuditEvent.SET_OTP_SECRET,
+                        pwmSession.getUserInfoBean(),
+                        pwmSession
+                );
+                pwmApplication.getAuditManager().submit(auditRecord);
+
+
                 if (pwmApplication.getStatisticsManager() != null && pwmApplication.getStatisticsManager().status() == PwmService.STATUS.OPEN) {
                     pwmApplication.getStatisticsManager().incrementValue(Statistic.SETUP_OTP_SECRET);
                 }

+ 1 - 1
pwm/servlet/src/password/pwm/http/servlet/SetupResponsesServlet.java

@@ -70,7 +70,7 @@ import java.util.*;
 @WebServlet(
         name="SetupResponsesServlet",
         urlPatterns={
-                PwmConstants.URL_PREFIX_PRIVATE + "/setupresponses",
+                PwmConstants.URL_PREFIX_PRIVATE + "/setup-responses",
                 PwmConstants.URL_PREFIX_PRIVATE + "/SetupResponses",
         }
 )

+ 85 - 203
pwm/servlet/src/password/pwm/http/servlet/ConfigManagerServlet.java → pwm/servlet/src/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -20,16 +20,15 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm.http.servlet;
+package password.pwm.http.servlet.configmanager;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.*;
 import password.pwm.config.Configuration;
-import password.pwm.config.ConfigurationReader;
-import password.pwm.config.PwmSetting;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
@@ -37,13 +36,13 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.*;
 import password.pwm.http.bean.ConfigManagerBean;
+import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.http.servlet.ConfigGuideServlet;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.Display;
 import password.pwm.i18n.LocaleHelper;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.util.*;
-import password.pwm.util.intruder.RecordType;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBFactory;
 import password.pwm.util.localdb.LocalDBUtility;
@@ -51,14 +50,10 @@ import password.pwm.util.logging.LocalDBLogger;
 import password.pwm.util.logging.PwmLogEvent;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmHashAlgorithm;
-import password.pwm.util.secure.PwmSecurityKey;
-import password.pwm.util.secure.SecureEngine;
 import password.pwm.ws.server.RestResultBean;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import java.io.*;
 import java.lang.management.ManagementFactory;
@@ -84,6 +79,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         exportLocalDB(HttpMethod.GET),
         generateSupportZip(HttpMethod.GET),
         uploadConfig(HttpMethod.POST),
+        uploadWordlist(HttpMethod.POST),
         importLocalDB(HttpMethod.POST),
         summary(HttpMethod.GET),
         viewLog(HttpMethod.GET),
@@ -119,10 +115,6 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final ConfigManagerBean configManagerBean = pwmSession.getConfigManagerBean();
 
-        if (checkAuthentication(pwmRequest, configManagerBean)) {
-            return;
-        }
-
         final ConfigManagerAction processAction = readProcessAction(pwmRequest);
         if (processAction != null) {
             switch (processAction) {
@@ -150,6 +142,10 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
                     ConfigGuideServlet.restUploadConfig(pwmRequest);
                     return;
 
+                case uploadWordlist:
+                    restUploadWordlist(pwmRequest);
+                    return;
+
                 case importLocalDB:
                     restUploadLocalDB(pwmRequest);
                     return;
@@ -239,167 +235,44 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
     }
 
-    static boolean checkAuthentication(
-            final PwmRequest pwmRequest,
-            final ConfigManagerBean configManagerBean
-    )
-            throws IOException, PwmUnrecoverableException, ServletException
+    void restUploadWordlist(final PwmRequest pwmRequest)
+            throws IOException, ServletException, PwmUnrecoverableException
+
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final ConfigurationReader runningConfigReader = ContextManager.getContextManager(pwmRequest.getHttpServletRequest().getSession()).getConfigReader();
-        final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration();
-
-        boolean authRequired = false;
-        if (storedConfig.hasPassword()) {
-            authRequired = true;
-        }
-
-        if (PwmApplication.MODE.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) {
-            if (!pwmSession.getSessionStateBean().isAuthenticated()) {
-                throw new PwmUnrecoverableException(PwmError.ERROR_AUTHENTICATION_REQUIRED);
-            }
-
-            if (pwmSession.getLoginInfoBean().getAuthenticationType() != AuthenticationType.AUTHENTICATED) {
-                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_AUTHENTICATION_REQUIRED,
-                        "Username/Password authentication is required to edit configuration.  This session has not been authenticated using a user password (SSO or other method used)."));
-            }
-        }
-
-        if (PwmApplication.MODE.CONFIGURATION != pwmRequest.getPwmApplication().getApplicationMode()) {
-            authRequired = true;
-        }
-
-        if (!authRequired) {
-            return false;
-        }
+        final HttpServletRequest req = pwmRequest.getHttpServletRequest();
 
-        if (!storedConfig.hasPassword()) {
-            final String errorMsg = "config file does not have a configuration password";
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg,new String[]{errorMsg});
+        /*
+        if (pwmApplication.getApplicationMode() == PwmApplication.MODE.RUNNING) {
+            final String errorMsg = "wordlist upload is not permitted when in running mode";
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,errorMsg,new String[]{errorMsg});
             pwmRequest.respondWithError(errorInformation, true);
-            return true;
-        }
-
-        if (configManagerBean.isPasswordVerified()) {
-            return false;
-        }
-
-        String persistentLoginValue = null;
-        boolean persistentLoginAccepted = false;
-        boolean persistentLoginEnabled = false;
-        if (pwmRequest.getConfig().isDefaultValue(PwmSetting.PWM_SECURITY_KEY)) {
-            LOGGER.debug(pwmRequest, "security not available, persistent login not possible.");
-        } else {
-            persistentLoginEnabled = true;
-            final PwmSecurityKey securityKey = pwmRequest.getConfig().getSecurityKey();
-
-            if (PwmApplication.MODE.RUNNING == pwmRequest.getPwmApplication().getApplicationMode()) {
-                persistentLoginValue = SecureEngine.hash(
-                        storedConfig.readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_PASSWORD_HASH)
-                                + pwmSession.getUserInfoBean().getUserIdentity().toDelimitedKey(),
-                        PwmHashAlgorithm.SHA512);
-
-            } else {
-                persistentLoginValue = SecureEngine.hash(
-                        storedConfig.readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_PASSWORD_HASH),
-                        PwmHashAlgorithm.SHA512);
-            }
-
-            {
-                final String cookieStr = ServletHelper.readCookie(
-                        pwmRequest.getHttpServletRequest(),
-                        PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN
-                );
-                if (securityKey != null && cookieStr != null && !cookieStr.isEmpty()) {
-                    try {
-                        final String jsonStr = pwmApplication.getSecureService().decryptStringValue(cookieStr);
-                        final PersistentLoginInfo persistentLoginInfo = JsonUtil.deserialize(jsonStr, PersistentLoginInfo.class);
-                        if (persistentLoginInfo != null && persistentLoginValue != null) {
-                            if (persistentLoginInfo.getExpireDate().after(new Date())) {
-                                if (persistentLoginValue.equals(persistentLoginInfo.getPassword())) {
-                                    persistentLoginAccepted = true;
-                                    LOGGER.debug(pwmRequest, "accepting persistent config login from cookie (expires "
-                                                    + PwmConstants.DEFAULT_DATETIME_FORMAT.format(persistentLoginInfo.getExpireDate())
-                                                    + ")"
-                                    );
-                                }
-                            }
-                        }
-                    } catch (Exception e) {
-                        LOGGER.error(pwmRequest, "error examining persistent config login cookie: " + e.getMessage());
-                    }
-                    if (!persistentLoginAccepted) {
-                        Cookie removalCookie = new Cookie(PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN, null);
-                        removalCookie.setMaxAge(0);
-                        pwmRequest.getPwmResponse().addCookie(removalCookie);
-                        LOGGER.debug(pwmRequest, "removing non-working persistent config login cookie");
-                    }
-                }
-            }
-        }
-
-
-        final String password = pwmRequest.readParameterAsString("password");
-        boolean passwordAccepted = false;
-        if (!persistentLoginAccepted) {
-            if (password != null && password.length() > 0) {
-                if (storedConfig.verifyPassword(password)) {
-                    passwordAccepted = true;
-                    LOGGER.trace(pwmRequest, "valid configuration password accepted");
-                } else{
-                    LOGGER.trace(pwmRequest, "configuration password is not correct");
-                    pwmApplication.getIntruderManager().convenience().markAddressAndSession(pwmSession);
-                    pwmApplication.getIntruderManager().mark(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmSession.getLabel());
-                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_WRONGPASSWORD);
-                    pwmRequest.setResponseError(errorInformation);
-                }
-            }
+            return;
         }
+        */
 
-        final int persistentSeconds = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS));
-        if ((persistentLoginAccepted || passwordAccepted)) {
-            configManagerBean.setPasswordVerified(true);
-            pwmApplication.getIntruderManager().convenience().clearAddressAndSession(pwmSession);
-            pwmApplication.getIntruderManager().clear(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME);
-            if (persistentLoginEnabled && !persistentLoginAccepted && "on".equals(pwmRequest.readParameterAsString("remember"))) {
-                if (persistentSeconds > 0) {
-                    final Date expirationDate = new Date(System.currentTimeMillis() + (persistentSeconds * 1000));
-                    final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo(expirationDate, persistentLoginValue);
-                    final String jsonPersistentLoginInfo = JsonUtil.serialize(persistentLoginInfo);
-                    final String cookieValue = pwmApplication.getSecureService().encryptToString(jsonPersistentLoginInfo);
-                    pwmRequest.getPwmResponse().writeCookie(
-                            PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN,
-                            cookieValue,
-                            persistentSeconds,
-                            true
-                    );
-                    LOGGER.debug(pwmRequest, "set persistent config login cookie (expires "
-                                    + PwmConstants.DEFAULT_DATETIME_FORMAT.format(expirationDate)
-                                    + ")"
-                    );
-                }
-            }
-
-            if (configManagerBean.getPrePasswordEntryUrl() != null) {
-                final String originalUrl = configManagerBean.getPrePasswordEntryUrl();
-                configManagerBean.setPrePasswordEntryUrl(null);
-                pwmRequest.getPwmResponse().sendRedirect(originalUrl);
-                return true;
-            }
-            return false;
+        if (!ServletFileUpload.isMultipartContent(req)) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,"no file found in upload");
+            pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
+            LOGGER.error(pwmRequest, "error during import: " + errorInformation.toDebugStr());
+            return;
         }
 
-        if (configManagerBean.getPrePasswordEntryUrl() == null) {
-            configManagerBean.setPrePasswordEntryUrl(pwmRequest.getHttpServletRequest().getRequestURL().toString());
+        final InputStream inputStream = ServletHelper.readFileUpload(pwmRequest.getHttpServletRequest(),"uploadFile");
+        try {
+            pwmApplication.getWordlistManager().populate(inputStream);
+        } catch (PwmUnrecoverableException e) {
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage());
+            final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest);
+            LOGGER.debug(pwmRequest, errorInfo.toDebugStr());
+            pwmRequest.outputJsonResult(restResultBean);
+            return;
         }
 
-        final String time = new TimeDuration(persistentSeconds * 1000).asLongString(pwmRequest.getLocale());
-        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigPasswordRememberTime,time);
-        pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_LOGIN);
-        return true;
+        pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
     }
 
+
     private void doStartEditing(final PwmRequest pwmRequest)
             throws IOException, PwmUnrecoverableException, ServletException
     {
@@ -421,7 +294,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         }
 
         if (!pwmSession.getSessionStateBean().isAuthenticated()) {
-            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_AUTHENTICATION_REQUIRED,"You must be authenticated before locking the configuration");
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_AUTHENTICATION_REQUIRED,"You must be authenticated before restricting the configuration");
             final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest);
             LOGGER.debug(pwmSession, errorInfo);
             pwmRequest.outputJsonResult(restResultBean);
@@ -429,7 +302,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         }
 
         if (!pwmSession.getSessionManager().checkPermission(pwmApplication, Permission.PWMADMIN)) {
-            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"You must be authenticated with admin privileges before locking the configuration");
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,"You must be authenticated with admin privileges before restricting the configuration");
             final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest);
             LOGGER.debug(pwmSession, errorInfo);
             pwmRequest.outputJsonResult(restResultBean);
@@ -437,16 +310,16 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         }
 
         try {
-            final StoredConfiguration storedConfiguration = readCurrentConfiguration(pwmRequest);
+            final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest);
             if (!storedConfiguration.hasPassword()) {
-                final ErrorInformation errorInfo = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"Please set a configuration password before locking the configuration"});
+                final ErrorInformation errorInfo = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"Please set a configuration password before restricting the configuration"});
                 final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest);
                 LOGGER.debug(pwmSession, errorInfo);
                 pwmRequest.outputJsonResult(restResultBean);
                 return;
             }
 
-            storedConfiguration.writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE, "false");
+            storedConfiguration.writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE, "false");
             saveConfiguration(pwmRequest, storedConfiguration);
             final ConfigManagerBean configManagerBean = pwmSession.getConfigManagerBean();
             configManagerBean.setConfiguration(null);
@@ -468,9 +341,9 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         pwmRequest.outputJsonResult(new RestResultBean(resultData));
     }
 
-    static void saveConfiguration(
+    public static void saveConfiguration(
             final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration
+            final StoredConfigurationImpl storedConfiguration
     )
             throws PwmUnrecoverableException
     {
@@ -508,7 +381,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         final PwmResponse resp = pwmRequest.getPwmResponse();
 
         try {
-            final StoredConfiguration storedConfiguration = readCurrentConfiguration(pwmRequest);
+            final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest);
             final OutputStream responseWriter = resp.getOutputStream();
             resp.setHeader(PwmConstants.HttpHeader.ContentDisposition, "attachment;filename=" + PwmConstants.DEFAULT_CONFIG_FILE_FILENAME);
             resp.setContentType(PwmConstants.ContentTypeValue.xml);
@@ -582,13 +455,13 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         }
     }
 
-    static StoredConfiguration readCurrentConfiguration(final PwmRequest pwmRequest)
+    public static StoredConfigurationImpl readCurrentConfiguration(final PwmRequest pwmRequest)
             throws PwmUnrecoverableException
     {
         final ContextManager contextManager = ContextManager.getContextManager(pwmRequest.getHttpServletRequest().getSession());
         final ConfigurationReader runningConfigReader = contextManager.getConfigReader();
-        final StoredConfiguration runningConfig = runningConfigReader.getStoredConfiguration();
-        return StoredConfiguration.copy(runningConfig);
+        final StoredConfigurationImpl runningConfig = runningConfigReader.getStoredConfiguration();
+        return StoredConfigurationImpl.copy(runningConfig);
     }
 
     private void doExportLocalDB(final PwmRequest pwmRequest)
@@ -610,35 +483,10 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         }
     }
 
-    private static class PersistentLoginInfo implements Serializable {
-        private Date expireDate;
-        private String password;
-
-        private PersistentLoginInfo(
-                Date expireDate,
-                String password
-        )
-        {
-            this.expireDate = expireDate;
-            this.password = password;
-        }
-
-        public Date getExpireDate()
-        {
-            return expireDate;
-        }
-
-        public String getPassword()
-        {
-            return password;
-        }
-    }
-
-
     private void restSummary(final PwmRequest pwmRequest)
             throws IOException, ServletException, PwmUnrecoverableException
     {
-        final StoredConfiguration storedConfiguration = readCurrentConfiguration(pwmRequest);
+        final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest);
         final LinkedHashMap<String,Object> outputMap = new LinkedHashMap<>(storedConfiguration.toOutputMap(pwmRequest.getLocale()));
         pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigurationSummaryOutput,outputMap);
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_EDITOR_SUMMARY);
@@ -679,7 +527,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         @Override
         public void outputItem(PwmApplication pwmApplication, PwmRequest pwmRequest, OutputStream outputStream) throws Exception
         {
-            final StoredConfiguration storedConfiguration = readCurrentConfiguration(pwmRequest);
+            final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest);
             storedConfiguration.resetAllPasswordValues("value removed from " + PwmConstants.PWM_APP_NAME + "-Support configuration export");
             final String jsonOutput = JsonUtil.serialize(storedConfiguration.toJsonDebugObject(), JsonUtil.Flag.PrettyPrint);
             outputStream.write(jsonOutput.getBytes(PwmConstants.DEFAULT_CHARSET));
@@ -695,7 +543,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         @Override
         public void outputItem(PwmApplication pwmApplication, PwmRequest pwmRequest, OutputStream outputStream) throws Exception
         {
-            final StoredConfiguration storedConfiguration = readCurrentConfiguration(pwmRequest);
+            final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest);
             storedConfiguration.resetAllPasswordValues("value removed from " + PwmConstants.PWM_APP_NAME + "-Support configuration export");
 
             final StringWriter writer = new StringWriter();
@@ -729,7 +577,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
         @Override
         public void outputItem(PwmApplication pwmApplication, PwmRequest pwmRequest, OutputStream outputStream) throws Exception
         {
-            final StoredConfiguration storedConfiguration = readCurrentConfiguration(pwmRequest);
+            final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration(pwmRequest);
             storedConfiguration.resetAllPasswordValues("value removed from " + PwmConstants.PWM_APP_NAME + "-Support configuration export");
 
             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -975,5 +823,39 @@ public class ConfigManagerServlet extends AbstractPwmServlet {
             LOGGER.trace("output " + counter + " lines to " + this.getFilename());
         }
     }
+
+    public enum Page {
+        manager(PwmConstants.JSP_URL.ADMIN_DASHBOARD,"/manager"),
+        wordlists(PwmConstants.JSP_URL.ADMIN_ANALYSIS,"/wordlists"),
+
+        ;
+
+        private final PwmConstants.JSP_URL jspURL;
+        private final String urlSuffix;
+
+        Page(PwmConstants.JSP_URL jspURL, String urlSuffix) {
+            this.jspURL = jspURL;
+            this.urlSuffix = urlSuffix;
+        }
+
+        public PwmConstants.JSP_URL getJspURL() {
+            return jspURL;
+        }
+
+        public String getUrlSuffix() {
+            return urlSuffix;
+        }
+
+        public static Page forUrl(final PwmURL pwmURL) {
+            final String url = pwmURL.toString();
+            for (final Page page : Page.values()) {
+                if (url.endsWith(page.urlSuffix)) {
+                    return page;
+                }
+            }
+            return null;
+        }
+    }
+
 }
 

+ 260 - 0
pwm/servlet/src/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java

@@ -0,0 +1,260 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://code.google.com/p/pwm/
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2015 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.configmanager;
+
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.i18n.Config;
+import password.pwm.i18n.Display;
+import password.pwm.i18n.LocaleHelper;
+import password.pwm.i18n.Message;
+import password.pwm.util.Helper;
+import password.pwm.util.ServletHelper;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.wordlist.StoredWordlistDataBean;
+import password.pwm.wordlist.WordlistType;
+import password.pwm.ws.server.RestResultBean;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.*;
+
+@WebServlet(
+        name = "ConfigManagerWordlistServlet",
+        urlPatterns = {
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/manager/wordlists",
+        }
+)
+public class ConfigManagerWordlistServlet extends AbstractPwmServlet {
+    final static private PwmLogger LOGGER = PwmLogger.forClass(ConfigManagerWordlistServlet.class);
+
+    public enum ConfigManagerAction implements ProcessAction {
+        clearWordlist(HttpMethod.POST),
+        uploadWordlist(HttpMethod.POST),
+        readWordlistData(HttpMethod.POST),
+
+        ;
+
+        private final HttpMethod method;
+
+        ConfigManagerAction(HttpMethod method)
+        {
+            this.method = method;
+        }
+
+        public Collection<HttpMethod> permittedMethods()
+        {
+            return Collections.singletonList(method);
+        }
+    }
+
+    protected ConfigManagerAction readProcessAction(final PwmRequest request)
+            throws PwmUnrecoverableException
+    {
+        try {
+            return ConfigManagerAction.valueOf(request.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST));
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    protected void processAction(final PwmRequest pwmRequest)
+            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
+    {
+
+        final ConfigManagerAction processAction = readProcessAction(pwmRequest);
+        if (processAction != null) {
+            switch (processAction) {
+                case uploadWordlist:
+                    restUploadWordlist(pwmRequest);
+                    return;
+
+                case clearWordlist:
+                    restClearWordlist(pwmRequest);
+                    return;
+
+                case readWordlistData:
+                    restReadWordlistData(pwmRequest);
+                    return;
+            }
+            return;
+        }
+
+        initRequestAttributes(pwmRequest);
+        pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_WORDLISTS);
+    }
+
+    void initRequestAttributes(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException
+    {
+        final ConfigurationReader configurationReader = pwmRequest.getContextManager().getConfigReader();
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.PageTitle,LocaleHelper.getLocalizedMessage(Config.Title_ConfigManager, pwmRequest));
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ApplicationPath, pwmRequest.getPwmApplication().getApplicationPath().getAbsolutePath());
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigFilename, configurationReader.getConfigFile().getAbsolutePath());
+        {
+            final Date lastModifyTime = configurationReader.getStoredConfiguration().modifyTime();
+            final String output = lastModifyTime == null
+                    ? LocaleHelper.getLocalizedMessage(Display.Value_NotApplicable,pwmRequest)
+                    : PwmConstants.DEFAULT_DATETIME_FORMAT.format(lastModifyTime);
+            pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigLastModified, output);
+        }
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigHasPassword, LocaleHelper.booleanString(configurationReader.getStoredConfiguration().hasPassword(), pwmRequest.getLocale(), pwmRequest.getConfig()));
+    }
+
+
+    void restUploadWordlist(final PwmRequest pwmRequest)
+            throws IOException, ServletException, PwmUnrecoverableException
+
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final HttpServletRequest req = pwmRequest.getHttpServletRequest();
+        final String wordlistTypeParam = pwmRequest.readParameterAsString("wordlist");
+        final WordlistType wordlistType = WordlistType.valueOf(wordlistTypeParam);
+
+        if (wordlistType == null) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,"unknown wordlist type: " + wordlistTypeParam);
+            pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
+            LOGGER.error(pwmRequest, "error during import: " + errorInformation.toDebugStr());
+            return;
+        }
+
+        if (!ServletFileUpload.isMultipartContent(req)) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,"no file found in upload");
+            pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
+            LOGGER.error(pwmRequest, "error during import: " + errorInformation.toDebugStr());
+            return;
+        }
+
+        final InputStream inputStream = ServletHelper.readFileUpload(pwmRequest.getHttpServletRequest(),"uploadFile");
+
+        try {
+            wordlistType.forType(pwmApplication).populate(inputStream);
+        } catch (PwmUnrecoverableException e) {
+            final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getMessage());
+            final RestResultBean restResultBean = RestResultBean.fromError(errorInfo, pwmRequest);
+            LOGGER.debug(pwmRequest, errorInfo.toDebugStr());
+            pwmRequest.outputJsonResult(restResultBean);
+            return;
+        }
+
+        pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
+    }
+
+    void restClearWordlist(final PwmRequest pwmRequest)
+            throws IOException, PwmUnrecoverableException
+    {
+        final String wordlistTypeParam = pwmRequest.readParameterAsString("wordlist");
+        final WordlistType wordlistType = WordlistType.valueOf(wordlistTypeParam);
+
+        if (wordlistType == null) {
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,"unknown wordlist type: " + wordlistTypeParam);
+            pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest));
+            LOGGER.error(pwmRequest, "error during clear: " + errorInformation.toDebugStr());
+            return;
+        }
+
+        final Thread thread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    wordlistType.forType(pwmRequest.getPwmApplication()).populateBuiltIn();
+                } catch (Exception e) {
+                    LOGGER.error("error clearing wordlist " + wordlistType + ", error: " + e.getMessage());
+                }
+            }
+        });
+        thread.setDaemon(true);
+        thread.setName(Helper.makeThreadName(pwmRequest.getPwmApplication(), ConfigManagerWordlistServlet.class) + "-" + wordlistType + "-Clear");
+        thread.start();
+
+        pwmRequest.outputJsonResult(RestResultBean.forSuccessMessage(pwmRequest, Message.Success_Unknown));
+    }
+
+    void restReadWordlistData(final PwmRequest pwmRequest)
+            throws IOException
+    {
+        final LinkedHashMap<WordlistType,WordlistDataBean> outputData = new LinkedHashMap<>();
+        for (WordlistType wordlistType : WordlistType.values()) {
+
+            final StoredWordlistDataBean storedWordlistDataBean = wordlistType.forType(pwmRequest.getPwmApplication()).readMetadata();
+
+            final Map<String,String> presentableValues = new LinkedHashMap<>();
+            presentableValues.put("Population Status", storedWordlistDataBean.isCompleted() ? "Completed" : "In-Progress");
+            if (storedWordlistDataBean.isCompleted()) {
+                presentableValues.put("Wordlist Type", storedWordlistDataBean.isBuiltin() ? "Built-In" : "Uploaded");
+                presentableValues.put("Word Count", String.valueOf(storedWordlistDataBean.getSize()));
+                if (!storedWordlistDataBean.isBuiltin()) {
+                    presentableValues.put("Population Timestamp", PwmConstants.DEFAULT_DATETIME_FORMAT.format(storedWordlistDataBean.getStoreDate()));
+                }
+                presentableValues.put("SHA1 Checksum Hash", storedWordlistDataBean.getSha1hash());
+            }
+
+            final WordlistDataBean wordlistDataBean = new WordlistDataBean();
+            wordlistDataBean.getPresentableData().putAll(presentableValues);
+            wordlistDataBean.setCompleted(storedWordlistDataBean.isCompleted());
+            wordlistDataBean.setBuiltIn(storedWordlistDataBean.isBuiltin());
+            outputData.put(wordlistType,wordlistDataBean);
+        }
+        pwmRequest.outputJsonResult(new RestResultBean(outputData));
+    }
+
+    public static class WordlistDataBean implements Serializable {
+        private Map<String,String> presentableData = new LinkedHashMap<>();
+        private boolean completed;
+        private boolean builtIn;
+
+        public Map<String, String> getPresentableData() {
+            return presentableData;
+        }
+
+        public boolean isCompleted() {
+            return completed;
+        }
+
+        public void setCompleted(boolean completed) {
+            this.completed = completed;
+        }
+
+        public boolean isBuiltIn() {
+            return builtIn;
+        }
+
+        public void setBuiltIn(boolean builtIn) {
+            this.builtIn = builtIn;
+        }
+    }
+}
+

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

@@ -59,9 +59,9 @@ import password.pwm.ldap.UserStatusReader;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationUtility;
 import password.pwm.ldap.auth.SessionAuthenticator;
-import password.pwm.token.TokenType;
 import password.pwm.token.TokenPayload;
 import password.pwm.token.TokenService;
+import password.pwm.token.TokenType;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PostChangePasswordAction;
@@ -1113,7 +1113,7 @@ public class ForgottenPasswordServlet extends AbstractPwmServlet {
             }
         };
 
-        pwmRequest.getPwmSession().getLoginInfoBean().addPostChangePasswordActions("forgottenPasswordPostActions", postAction);
+        pwmRequest.getPwmSession().getUserSessionDataCacheBean().addPostChangePasswordActions("forgottenPasswordPostActions", postAction);
     }
 
     private static void verifyRequirementsForAuthMethod(

+ 24 - 8
pwm/servlet/src/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -38,6 +38,8 @@ import password.pwm.error.*;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.i18n.Display;
+import password.pwm.i18n.LocaleHelper;
 import password.pwm.ldap.*;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.TimeDuration;
@@ -198,11 +200,13 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
 
         final String username = Validator.sanitizeInputValue(pwmRequest.getConfig(), valueMap.get("username"), 1024);
         final CacheKey cacheKey = makeCacheKey(pwmRequest, "search", username);
-        {
+
+        { // try to serve from cache first
             final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
             if (cachedOutput != null) {
-                final SearchResultBean resultOutput = JsonUtil.deserialize(cachedOutput, SearchResultBean.class);
-                pwmRequest.outputJsonResult(new RestResultBean(resultOutput));
+                final SearchResultBean searchResultBean = JsonUtil.deserialize(cachedOutput, SearchResultBean.class);
+                searchResultBean.setFromCache(true);
+                pwmRequest.outputJsonResult(new RestResultBean(searchResultBean));
                 StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS);
                 return;
             } else {
@@ -210,11 +214,14 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
             }
         }
 
-        final SearchResultBean outputData = makeSearchResultsImpl(pwmRequest, username);
-        final RestResultBean restResultBean = new RestResultBean(outputData);
+        // if not in cache, build results from ldap
+        final SearchResultBean searchResultBean = makeSearchResultsImpl(pwmRequest, username);
+        searchResultBean.setFromCache(false);
+        final RestResultBean restResultBean = new RestResultBean(searchResultBean);
         pwmRequest.outputJsonResult(restResultBean);
         StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_SEARCHES);
-        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, outputData);
+        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, searchResultBean);
+        LOGGER.trace(pwmRequest, "returning " + searchResultBean.getSearchResults().size() + " results for search request '" + username + "'");
     }
 
     private SearchResultBean makeSearchResultsImpl(
@@ -259,12 +266,21 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
             throw new PwmUnrecoverableException(errorInformation);
         }
 
-        LOGGER.trace(pwmRequest.getPwmSession(), "finished rest peoplesearch search in " + TimeDuration.fromCurrent(
-                startTime).asCompactString() + " not using cache, size=" + results.getResults().size());
+        final TimeDuration searchDuration = TimeDuration.fromCurrent(startTime);
+        LOGGER.trace(pwmRequest.getPwmSession(), "finished rest peoplesearch search in " +
+                searchDuration.asCompactString() + " not using cache, size=" + results.getResults().size());
 
         final SearchResultBean searchResultBean = new SearchResultBean();
         searchResultBean.setSearchResults(new ArrayList<>(results.resultsAsJsonOutput(pwmRequest.getPwmApplication())));
         searchResultBean.setSizeExceeded(sizeExceeded);
+        final String aboutMessage = LocaleHelper.getLocalizedMessage(
+                pwmRequest.getLocale(),
+                Display.Display_SearchResultsInfo.getKey(),
+                pwmRequest.getConfig(),
+                Display.class,
+                new String[]{String.valueOf(results.getResults().size()), searchDuration.asLongString(pwmRequest.getLocale())}
+        );
+        searchResultBean.setAboutResultMessage(aboutMessage);
         return searchResultBean;
     }
 

+ 18 - 0
pwm/servlet/src/password/pwm/http/servlet/peoplesearch/SearchResultBean.java

@@ -29,6 +29,8 @@ import java.util.List;
 class SearchResultBean implements Serializable {
     private List searchResults = new ArrayList<>();
     private boolean sizeExceeded;
+    private String aboutResultMessage;
+    private boolean fromCache;
 
     public List getSearchResults() {
         return searchResults;
@@ -45,4 +47,20 @@ class SearchResultBean implements Serializable {
     public void setSizeExceeded(boolean sizeExceeded) {
         this.sizeExceeded = sizeExceeded;
     }
+
+    public String getAboutResultMessage() {
+        return aboutResultMessage;
+    }
+
+    public void setAboutResultMessage(String aboutResultMessage) {
+        this.aboutResultMessage = aboutResultMessage;
+    }
+
+    public boolean isFromCache() {
+        return fromCache;
+    }
+
+    public void setFromCache(boolean fromCache) {
+        this.fromCache = fromCache;
+    }
 }

+ 1 - 1
pwm/servlet/src/password/pwm/http/servlet/resource/ResourceServletConfiguration.java

@@ -114,7 +114,7 @@ class ResourceServletConfiguration {
                         final File zipFileFile = new File(pwmApplication.getWebInfPath().getParentFile() + loopInitParam.getZipFile());
                         final ZipFile zipFile = new ZipFile(zipFileFile);
                         zipResources.put(loopInitParam.getUrl(), zipFile);
-                        LOGGER.error("registered resource-zip file " + loopInitParam.getZipFile() + " at path " + zipFileFile.getAbsolutePath());
+                        LOGGER.debug("registered resource-zip file " + loopInitParam.getZipFile() + " at path " + zipFileFile.getAbsolutePath());
                     } catch (IOException e) {
                         LOGGER.warn("unable to resource-zip file " + loopInitParam + ", error: " + e.getMessage());
                     }

+ 1 - 1
pwm/servlet/src/password/pwm/http/tag/PwmIfTag.java

@@ -112,7 +112,7 @@ public class PwmIfTag extends BodyTagSupport {
         return argsList.isEmpty() ? null : argsList.toArray(new String[argsList.size()]);
     }
 
-    enum TESTS {
+    public enum TESTS {
         authenticated(new AuthenticatedTest()),
         configurationOpen(new ConfigurationOpen()),
         showIcons(new BooleanAppPropertyTest(AppProperty.CLIENT_JSP_SHOW_ICONS)),

+ 6 - 6
pwm/servlet/src/password/pwm/i18n/Config.properties

@@ -28,7 +28,7 @@ Button_CheckSettings=Check Settings
 Button_ShowAdvanced=Show %1% Advanced Settings
 Button_HideAdvanced=Hide Advanced Settings
 Confirm_RemoveProfile=Are you sure you want to remove the profile <code>%1%</code>?  The setting values associated with this profile will also be removed.
-Confirm_LockConfig=Are you sure you want to close the configuration?  After you close the configuration, you must authenticate using your LDAP directory credentials before authenticating, so be sure your LDAP configuration is working properly before closing.
+Confirm_LockConfig=Are you sure you want to restrict the configuration?  After you restrict the configuration, you must authenticate using your LDAP directory credentials before authenticating, so be sure your LDAP configuration is working properly before closing.
 Confirm_SkipGuide=Are you sure you want to skip the configuration guide?
 Confirm_UploadConfig=Are you sure you wish to overwrite the current running configuration with the selected file?
 Confirm_UploadLocalDB=Are you sure you wish to upload and replace the existing LocalDB contents with a previously exported LocalDB archive file?  <p>This operation may take a long time to complete, depending on the size of the archive.</p><p>During the upload, the application will not be available.  If the operation does not complete, the LocalDB will be emptied.</p>
@@ -42,9 +42,9 @@ Display_ConfigGuideLdapSchema=The status of the LDAP directory schema extension
 Display_ConfigGuideLdapSchema2=If you wish, you can extend the schema from this configuration guide.  In some cases, extending the LDAP directory schema may not be reversible, so use caution when performing this operation in any production environments.  Consult your LDAP directory product documentation for more information.
 Display_ConfigManagerConfiguration=<p>Welcome to the Configuration Manager. This system is in configuration mode, which means you can make changes to the running configuration directly through this page.</p><p>The current configuration was loaded at %1%.  (Epoch %2%)</p>
 Display_ConfigManagerNew=<p><b>Welcome to %1%.</b>  We hope you enjoy using this software.</p><p>%1% was not able to detect a pre-existing configuration and is now in new configuration mode.</p>
-Display_ConfigManagerRunning=<p><b>The configuration for this server has been closed.  However you can still edit the configuration.</b></p><p>For security reasons, to edit the configuration, you must upload (and then download) the <em>%1%</em> file.</p>
+Display_ConfigManagerRunning=<p><b>The configuration for this server has been restricted.  However you can still edit the configuration.</b></p><p>For security reasons, to edit the configuration, you must upload (and then download) the <em>%1%</em> file.</p>
 Display_ConfigManagerRunningEditor=Your modified configuration is currently in memory, but has not yet been saved.  Please choose an option below to continue.
-Display_ConfigOpenInfo=<p>While the configuration status is <i>open</i> any user can access this configuration without authenticating to LDAP.</p><p>Please close the configuration using the <a id="link-configManager">ConfigurationManager</a> when the configuration is completed enough to authenticate administrative users.</p>
+Display_ConfigOpenInfo=<p>While the configuration status is <i>open</i> any user can access this configuration without authenticating to LDAP.</p><p>Please restrict the configuration using the <a id="link-configManager">ConfigurationManager</a> when the configuration is completed enough to authenticate administrative users.</p>
 Display_SettingFilter_Level_0=Required
 Display_SettingFilter_Level_1=Standard / Common
 Display_SettingFilter_Level_2=Advanced
@@ -60,7 +60,7 @@ MenuDisplay_ConfigEditor=Use the configuration editor to edit the running config
 MenuDisplay_DownloadConfig=Download the current configuration XML file.
 MenuDisplay_DownloadConfigRunning=Download the in memory configuration to a file.  You can save the <em>%1%</em> file to the <em>WEB-INF</em> directory to change the configuration.  In most cases, the configuration will take effect immediately.
 MenuDisplay_DownloadBundle=Generate a support ZIP file that contains information useful for troubleshooting.
-MenuDisplay_LockConfig=Close the configuration. Once closed, you must be logged in with administrative access to edit the configuration, or you can edit the configuration file directly at <em>%1%</em>.
+MenuDisplay_LockConfig=Restrict the configuration. Once restricted, you must be logged in with administrative access to edit the configuration, or you can edit the configuration file directly at <em>%1%</em>.
 MenuDisplay_UnlockConfig=For security reasons, the configuration can not be opened through a web browser.  The configuration can be opened in either of the following ways: <ol><li>Edit the configuration with a text editor<ol><li>Use a UTF8 encoding compatible text editor (do not use Windows Notepad)</li><li>Open the file at <i>%1%</i></li><li>Locate the property <i>configIsEditable</i></li><li>Change the value to "true"</li></ol><li>Use the command line tool</li><ol><li>Open a command prompt and change to the same directory as the configuration file</li><li>Execute the <i>command.sh</i> or <i>command.bat</i> script as appropriate to your operating system.</li><li>Execute the script using the <i>ConfigUnlock</i> parameter.  Example: <i>./command.sh ConfigUnlock</i></li></ol></ol>
 MenuDisplay_ExportLocalDB=Export the contents of the LocalDB.  Can be used for backup or to restore to another server.
 MenuDisplay_MainMenu=Return to the main menu to test the configuration.
@@ -76,7 +76,7 @@ MenuItem_AlternateUnlockConfig=Alternate Option: Un-Locking the Configuration
 MenuItem_CancelEdits=Cancel Edits
 MenuItem_DownloadConfig=Download Configuration File
 MenuItem_DownloadBundle=Download Troubleshooting Bundle
-MenuItem_LockConfig=Close Configuration
+MenuItem_LockConfig=Restrict Configuration
 MenuItem_ExportLocalDB=Download LocalDB Archive File
 MenuItem_MainMenu=Main Menu
 MenuItem_ManualConfig=Manual Configuration
@@ -105,7 +105,7 @@ Warning_ShowAdvanced=<strong>Some settings are not displayed.</strong>&nbsp;&nbs
 Warning_ShowDescription=Help text for settings is available by clicking on setting title, or by selecting <em>Display Help Text</em> from the <em>View</em> menu.
 Warning_ShowNotes=Notes exist for this configuration.  Select <em>Configuration Notes</em> from the <em>View</em> menu to show the notes.
 Warning_HeaderVisibility=Drag to pointer to top of screen to re-enable the alert header bar.
-Warning_ConfigMustBeClosed=<p>The configuration must be closed before these settings are available.</p><p>Close the configuration using the <a href="%1%">ConfigManager</a>.</p>
+Warning_ConfigMustBeClosed=<p>The configuration must be restricted before these settings are available.</p><p>Restrict  the configuration using the <a href="%1%">ConfigManager</a>.</p>
 Warning_MakeSupportZipNoTrace=<b>Notice:</b> The configuration setting <code>@PwmSettingReference:events.pwmDB.logLevel@</code> is not set to level <code>TRACE</code>.  The download file may not contain all the debug information desired.
 Warning_DownloadSupportZip=<b>Warning:</b> The download file contains sensitive security information, handle with appropriate care.
 Warning_DownloadConfiguration=<b>Warning:</b> The configuration download file contains sensitive security information, including security credentials, handle with appropriate care.

+ 2 - 2
pwm/servlet/src/password/pwm/i18n/ConfigEditor.properties

@@ -45,7 +45,7 @@ Category_Description_GENERAL=General settings for the application.  Settings her
 Category_Description_GUEST=Note\: The guest user registration module requires that the logged in user has sufficient permissions to create users and, if so configured, to check for duplicate values.
 Category_Description_HELPDESK_PROFILE=Profile for helpdesk activities.
 Category_Description_HTTP_SSO=
-Category_Description_INTRUDER=This application has built in intruder detection independent of what your LDAP directory may provide.  Because this application may be exposed directly to the internet, this additional layer of detection helps protect against direct attacks.  The LDAP directory's own internal intruder detection (if enabled) will always be honored as well.  The goal for this intruder detection system isn't to watch for human intruders, it is designed to stop robot or automatic attacks.  It is recommended that the triggers be sufficiently high so that normal user usage will not cause an application-level intruder detection.
+Category_Description_INTRUDER=This application has built in intruder detection independent of what your LDAP directory may provide.  Because this application may be exposed directly to the internet, this additional layer of detection helps protect against direct attacks.  The LDAP directory's own internal intruder detection (if enabled) will always be honored as well.  The goal for this intruder detection system isn't to watch for human intruders, it is designed to stop robot or automatic attacks.  It is recommended that the triggers be sufficiently high so that normal user usage will not cause an application-level intruder detection.  Lockouts due to this intruder detection system can not be unlocked by the helpdesk or administrator.
 Category_Description_INTRUDER_SETTINGS=
 Category_Description_INTRUDER_TIMEOUTS=
 Category_Description_LDAP=
@@ -296,7 +296,7 @@ Setting_Description_helpdesk.detail.form=Field(s) to be shown during the detail
 Setting_Description_helpdesk.displayName=The display name used to identify the user on the user detail screen.  Macros are resolved to the displayed user.
 Setting_Description_helpdesk.enableUnlock=Enable helpdesk module users to unlock an (intruder) locked account.
 Setting_Description_helpdesk.enforcePasswordPolicy=Require that passwords set by helpdesk must meet the same password policy as the user would normally be constrained by.
-Setting_Description_helpdesk.filter=LDAP search filter to query the directory with.  Substitute <i>%USERNAME%</i> for user supplied username.  If not specified, a search filter will be auto calculated based on the Helpdesk Search Form.<p>Examples<ul><li>Edirectory\: <pre>(&(objectClass\=Person)(|((cn\=*%USERNAME%*)(uid\=*%USERNAME%*)(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*))))</pre></li><li>Active Directory\: <pre>(&(objectClass\=Person)(|((cn\=*%USERNAME%*)(uid\=*%USERNAME%*)(sAMAccountName\=*%USERNAME%*)(userprincipalname\=*%USERNAME%*)(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*))))</pre></li></ul>
+Setting_Description_helpdesk.filter=LDAP search filter to query the directory with.  Substitute <i>%USERNAME%</i> for user supplied username.  If not specified, a search filter will be auto calculated based on the Helpdesk Search Form.<p>Examples<ul><li>Edirectory\: <code>(&(objectClass\=Person)(|((cn\=*%USERNAME%*)(uid\=*%USERNAME%*)(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*))))</code></li><li>Active Directory\: <code>(&(objectClass\=Person)(|((cn\=*%USERNAME%*)(uid\=*%USERNAME%*)(sAMAccountName\=*%USERNAME%*)(userprincipalname\=*%USERNAME%*)(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*))))</code></li></ul>
 Setting_Description_helpdesk.forcePwExpiration=
 Setting_Description_helpdesk.idleTimeout=Number of seconds after which an authenticated session becomes unauthenticated.   Session Idle timeout will be set to this value once a user successfully access the helpdesk module.
 Setting_Description_helpdesk.otp.verify=Enable OTP Verification.

+ 1 - 0
pwm/servlet/src/password/pwm/i18n/Display.java

@@ -140,6 +140,7 @@ public enum Display implements PwmDisplayBundle {
     Display_ResponsesPrompt,
     Display_SelectionIndicator,
     Display_SearchCompleted,
+    Display_SearchResultsInfo,
     Display_SearchResultsExceeded,
     Display_SetRandomPasswordPrompt,
     Display_SearchResultsNone,

+ 1 - 0
pwm/servlet/src/password/pwm/i18n/Display.properties

@@ -139,6 +139,7 @@ Display_RecoverOTPIdentified=To verify your identity, please use your mobile dev
 Display_ResponsesPrompt=Please type your security answers
 Display_SelectionIndicator=Please select a question item from the list
 Display_SearchCompleted=Search completed.
+Display_SearchResultsInfo=Returned %1% results in %2%.
 Display_SearchResultsExceeded=Search results exceeded maximum search size.
 Display_SetRandomPasswordPrompt=Set a new random password for this user?
 Display_SearchResultsNone=No results.

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_cs.properties

@@ -153,7 +153,7 @@ Long_Title_UserEventHistory=Prohl\u00ed\u017ee\u010d histori\u00ed zm\u011bn hes
 Long_Title_UserInformation=Informace o heslu a politice hesel.
 Title_ActivateUser=Aktivovat u\u017eivatele
 Title_Admin=spr\u00e1vce
-Title_Application=Samoobslu\u017en\u00e1 zm\u011bna hesla
+Title_Application=
 Title_Captcha=Ov\u011b\u0159en\u00ed
 Title_ChangePassword=Zm\u011bnit heslo
 Title_ConfirmResponses=Potvrzen\u00ed reakce hesel

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_de.properties

@@ -243,7 +243,7 @@ Long_Title_UserInformation=Informationen \u00fcber Ihr Passwort und Passwortrich
 Title_AnsweredQuestions=Beantwortete Fragen
 Title_ActivateUser=Konto aktivieren
 Title_Admin=Verwaltung
-Title_Application=NetIQ Self Service Password Reset
+Title_Application=
 Title_Captcha=\u00dcberpr\u00fcfung
 Title_ChangePassword=Passwort \u00e4ndern
 Title_ConfirmResponses=Sicherheitsfragen best\u00e4tigen

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_el.properties

@@ -205,7 +205,7 @@ Long_Title_UserEventHistory = \u0399\u03c3\u03c4\u03bf\u03c1\u03b9\u03ba\u03cc \
 Long_Title_UserInformation = \u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03c7\u03b5\u03c4\u03b9\u03ba\u03ac \u03bc\u03b5 \u03c4\u03b7\u03bd \u03c0\u03bf\u03bb\u03b9\u03c4\u03b9\u03ba\u03ae \u03c0\u03bf\u03c5 \u03b1\u03c6\u03bf\u03c1\u03ac \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2.
 Title_ActivateUser = \u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u039b\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03bf\u03cd
 Title_Admin = \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7
-Title_Application = \u0395\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u0391\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u039a\u03c9\u03b4\u03b9\u03ba\u03ce\u03bd
+Title_Application =
 Title_Captcha = \u0395\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7
 Title_ChangePassword = \u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2
 Title_ConfirmResponses = \u0395\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03af\u03c9\u03c3\u03b7 \u03b1\u03c0\u03b1\u03bd\u03c4\u03ae\u03c3\u03b5\u03c9\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_es.properties

@@ -243,7 +243,7 @@ Long_Title_UserInformation=Informaci\u00f3n sobre su contrase\u00f1a y las direc
 Title_AnsweredQuestions=Preguntas contestadas
 Title_ActivateUser=Activar cuenta
 Title_Admin=Administraci\u00f3n
-Title_Application=NetIQ Self Service Password Reset
+Title_Application=
 Title_Captcha=Verificaci\u00f3n
 Title_ChangePassword=Cambiar contrase\u00f1a
 Title_ConfirmResponses=Confirmar preguntas de seguridad

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_fi.properties

@@ -128,7 +128,7 @@ Long_Title_UserEventHistory=Salasana tapahtumien historia tietoja, josta voit ka
 Long_Title_UserInformation=Tietoja salasanasta ja salasanapolitiikasta.
 Title_ActivateUser=Aktivoi k\u00e4ytt\u00e4j\u00e4
 Title_Admin=Admin
-Title_Application=Salasanan vaihto itsepalveluna
+Title_Application=
 Title_Captcha=Vahvistus
 Title_ChangePassword=Vaihda salasana
 Title_ConfirmResponses=Vahvista vastaukset

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_fr.properties

@@ -247,7 +247,7 @@ Long_Title_UserInformation=Informations sur votre mot de passe et sur les strat\
 Title_AnsweredQuestions=Questions auxquelles vous avez r\u00e9pondu
 Title_ActivateUser=Activer le compte
 Title_Admin=Administration
-Title_Application=NetIQ Self Service Password Reset
+Title_Application=
 Title_Captcha=V\u00e9rification
 Title_ChangePassword=\u00c9diter le mot de passe
 Title_ConfirmResponses=Confirmer les questions de s\u00e9curit\u00e9

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_hu.properties

@@ -171,7 +171,7 @@ Long_Title_UserEventHistory=A jelszav\u00e1val kapcsolatos esem\u00e9nyek list\u
 Long_Title_UserInformation=Inform\u00e1ci\u00f3k a jelszav\u00e1r\u00f3l ill. a jelsz\u00f3v\u00e1laszt\u00e1s szab\u00e1lyair\u00f3l.
 Title_ActivateUser=Bejelentkez\u00e9si jog aktiv\u00e1l\u00e1sa
 Title_Admin=PWM fel\u00fcgyelet
-Title_Application=\u00d6nkiszolg\u00e1l\u00f3 jelsz\u00f3karbantart\u00e1s
+Title_Application=
 Title_Captcha=Ellen\u0151rz\u00e9s
 Title_ChangePassword=Jelsz\u00f3v\u00e1ltoztat\u00e1s
 Title_ConfirmResponses=A v\u00e1laszok meger\u0151s\u00edt\u00e9se

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_ja.properties

@@ -243,7 +243,7 @@ Long_Title_UserInformation=\u30d1\u30b9\u30ef\u30fc\u30c9\u3068\u30d1\u30b9\u30e
 Title_AnsweredQuestions=\u56de\u7b54\u6e08\u307f\u306e\u8cea\u554f
 Title_ActivateUser=\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u6709\u52b9\u5316
 Title_Admin=\u7ba1\u7406
-Title_Application=NetIQ Self Service Password Reset
+Title_Application=
 Title_Captcha=\u78ba\u8a8d
 Title_ChangePassword=\u30d1\u30b9\u30ef\u30fc\u30c9\u5909\u66f4
 Title_ConfirmResponses=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u8cea\u554f\u306e\u78ba\u8a8d

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_ko.properties

@@ -162,7 +162,7 @@ Long_Title_UserEventHistory=\ud328\uc2a4\uc6cc\ub4dc\uc774\ubca4\ud2b8 \uae30\ub
 Long_Title_UserInformation=\ube44\ubc00 \ubc88\ud638\uc640 \ube44\ubc00 \ubc88\ud638 \uc815\ucc45\uc5d0 \ub300\ud55c \uc815\ubcf4.
 Title_ActivateUser=\uc0ac\uc6a9\uc790 \ud65c\uc131\ud654
 Title_Admin=\uad00\ub9ac\uc790
-Title_Application=\uc554\ud638 \uc140\ud504 \uc11c\ube44\uc2a4
+Title_Application=
 Title_AutoGeneratedPasswords=\uc790\ub3d9\uc0dd\uc131\ub41c \uc554\ud638\ub4e4
 Title_Captcha=\ud655\uc778
 Title_ChangePassword=\uc554\ud638 \ubcc0\uacbd

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_nl.properties

@@ -180,7 +180,7 @@ Long_Title_UserEventHistory=Gebruikershistorie inzien.
 Long_Title_UserInformation=Informatie over uw wachtwoord en wachtwoordbeleid.
 Title_ActivateUser=Gebruikersaccount activeren
 Title_Admin=Beheer
-Title_Application=Wachtwoordzelfbediening
+Title_Application=
 Title_Captcha=Verificatie
 Title_ChangePassword=Wachtwoord wijzigen
 Title_ConfirmResponses=Bevestig antwoorden

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_nn.properties

@@ -77,7 +77,7 @@ Button_Agree=Eg godtek
 Button_Cancel=Avbryt
 Button_More=Meir
 
-Title_Application=Passordsj�lvbetening
+Title_Application=
 Title_TitleBar=Passordsj�lvbetening
 Title_MainPage=Hovudmeny
 Title_Login=Ver venleg logg inn

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_no.properties

@@ -77,7 +77,7 @@ Button_Agree=Jeg godtar
 Button_Cancel=Avbryt
 Button_More=Mer
 
-Title_Application=Passordselvbetjening
+Title_Application=
 Title_TitleBar=Passordselvbetjening
 Title_MainPage=Hovedmeny
 Title_Login=Vennligst logg inn

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_pl.properties

@@ -153,7 +153,7 @@ Long_Title_UserEventHistory=Historia zdarze\u0144 has\u0142a. Zobacz kiedy zmien
 Long_Title_UserInformation=Informacje o twoim ha\u015ble i regu\u0142ach hase\u0142.
 Title_ActivateUser=Aktywuj u\u017cytkownika
 Title_Admin=Admin
-Title_Application=Automatyczna obs\u0142uga hase\u0142
+Title_Application=
 Title_Captcha=Weryfikacja
 Title_ChangePassword=Zmie\u0144 has\u0142o
 Title_ConfirmResponses=Potwierd\u017a Odpowiedzi dotycz\u0105ce hase\u0142

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_pt.properties

@@ -153,7 +153,7 @@ Long_Title_UserEventHistory=Hist\u00f3rico de eventos senha. Veja quando voc\u00
 Long_Title_UserInformation=Informa\u00e7\u00f5es sobre a sua senha e diretivas de senha.
 Title_ActivateUser=Ativar o Usu\u00e1rio
 Title_Admin=Administrador
-Title_Application=Self Service senha
+Title_Application=
 Title_Captcha=Verifica\u00e7\u00e3o
 Title_ChangePassword=Mudar a Senha
 Title_ConfirmResponses=Confirme respostas Senha

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_pt_BR.properties

@@ -243,7 +243,7 @@ Long_Title_UserInformation=Informa\u00e7\u00f5es sobre sua senha e pol\u00edtica
 Title_AnsweredQuestions=Perguntas Respondidas
 Title_ActivateUser=Ativar Conta
 Title_Admin=Administra\u00e7\u00e3o
-Title_Application=NetIQ Self Service Password Reset
+Title_Application=
 Title_Captcha=Verifica\u00e7\u00e3o
 Title_ChangePassword=Mudar Senha
 Title_ConfirmResponses=Confirmar Perguntas de Seguran\u00e7a

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_sk.properties

@@ -153,7 +153,7 @@ Long_Title_UserEventHistory=Hist\u00f3ria udalost\u00ed, t\u00fdkaj\u00facich sa
 Long_Title_UserInformation=Zobrazte si podrobn\u00e9 inform\u00e1cie o Va\u0161om \u00fa\u010dte (v angli\u010dtine).
 Title_ActivateUser=Aktivova\u0165 pou\u017e\u00edvate\u013ea
 Title_Admin=Administr\u00e1tor
-Title_Application=Port\u00e1l na spr\u00e1vu pou\u017e\u00edvate\u013esk\u00e9ho \u00fa\u010dtu
+Title_Application=
 Title_Captcha=Verifik\u00e1cia
 Title_ChangePassword=Zmena hesla
 Title_ConfirmResponses=Potvrdi\u0165 zadan\u00e9 heslo

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_sv.properties

@@ -243,7 +243,7 @@ Long_Title_UserInformation=Information om ditt l\u00f6senord och l\u00f6senordsr
 Title_AnsweredQuestions=Besvarade fr\u00e5gor
 Title_ActivateUser=Aktivera konto
 Title_Admin=Administration
-Title_Application=NetIQ Sj\u00e4lvbetj\u00e4ning f\u00f6r \u00e5terst\u00e4llning av l\u00f6senord
+Title_Application=
 Title_Captcha=Verifiering
 Title_ChangePassword=\u00c4ndra l\u00f6senord
 Title_ConfirmResponses=Bekr\u00e4fta s\u00e4kerhetsfr\u00e5gor

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_th.properties

@@ -162,7 +162,7 @@ Long_Title_UserEventHistory=\u0e1b\u0e23\u0e30\u0e27\u0e31\u0e15\u0e34\u0e02\u0e
 Long_Title_UserInformation=\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e41\u0e25\u0e30\u0e19\u0e42\u0e22\u0e1a\u0e32\u0e22
 Title_ActivateUser=\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e43\u0e2b\u0e49\u0e0a\u0e37\u0e48\u0e2d\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49\u0e17\u0e33\u0e07\u0e32\u0e19
 Title_Admin=\u0e1c\u0e39\u0e49\u0e14\u0e39\u0e41\u0e25\u0e23\u0e30\u0e1a\u0e1a
-Title_Application=\u0e01\u0e32\u0e23\u0e1a\u0e23\u0e34\u0e01\u0e32\u0e23\u0e01\u0e32\u0e23\u0e1a\u0e23\u0e34\u0e2b\u0e32\u0e23\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19\u0e14\u0e49\u0e27\u0e22\u0e15\u0e19\u0e40\u0e2d\u0e07
+Title_Application=
 Title_AutoGeneratedPasswords=\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19\u0e42\u0e14\u0e22\u0e2d\u0e31\u0e15\u0e42\u0e19\u0e21\u0e31\u0e15\u0e34
 Title_Captcha=\u0e15\u0e23\u0e27\u0e08\u0e2a\u0e2d\u0e1a
 Title_ChangePassword=\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Display_zh_TW.properties

@@ -243,7 +243,7 @@ Long_Title_UserInformation=\u5bc6\u78bc\u548c\u5bc6\u78bc\u898f\u5247\u76f8\u95d
 Title_AnsweredQuestions=\u5df2\u56de\u7b54\u554f\u984c
 Title_ActivateUser=\u555f\u52d5\u5e33\u6236
 Title_Admin=\u7ba1\u7406
-Title_Application=NetIQ Self Service Password Reset
+Title_Application=
 Title_Captcha=\u9a57\u8b49
 Title_ChangePassword=\u8b8a\u66f4\u5bc6\u78bc
 Title_ConfirmResponses=\u78ba\u8a8d\u5b89\u5168\u6027\u554f\u984c

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Health.properties

@@ -57,7 +57,7 @@ HealthMessage_Config_Certificate=Certificate for setting %1% issue: %2%
 HealthMessage_LDAP_VendorsNotSame=LDAP directories of different vendor types are in use.  This configuration may cause undesirable side effects and is not supported.  %1%
 HealthMessage_LDAP_Ad_History_Asn_Missing=%1% is enabled, but the server at %2% does not support this feature.  Check to be sure it is upgraded to Windows Server 2008 R2 SP1 or greater.  Password changes against this server may fail until this is resolved.
 HealthMessage_LDAP_RecentlyUnreachable=LDAP profile %1% was recently unavailable (%2% ago at %3%): %4%
-HealthMessage_Config_ConfigMode=Application is currently in <b>configuration</b> mode.   Anyone accessing this site can modify the configuration without a directory authentication.  When ready, lock the configuration to prevent unauthorized configuration changes.  The configuration can still be edited after closing but will require directory authentication first.
+HealthMessage_Config_ConfigMode=Application is currently in <b>configuration</b> mode.   Anyone accessing this site can modify the configuration without a directory authentication.  When ready, restrict the configuration to prevent unauthorized configuration changes.  The configuration can still be edited after closing but will require directory authentication first.
 HealthMessage_CryptoTokenWithNewUserVerification=%1% is enabled and %2% is set to LDAP, this configuration will not work.
 HealthMessage_TokenServiceError=An error occurred during the TokenService startup: %1%
 HealthMessage_Java_HighThreads=Java thread count is unusually large (%1% threads)

+ 19 - 7
pwm/servlet/src/password/pwm/ldap/LdapBrowser.java

@@ -31,19 +31,23 @@ import com.novell.ldapchai.util.ChaiUtility;
 import com.novell.ldapchai.util.SearchHelper;
 import password.pwm.AppProperty;
 import password.pwm.config.Configuration;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.logging.PwmLogger;
 
 import java.io.Serializable;
 import java.util.*;
 
 public class LdapBrowser {
-    final StoredConfiguration storedConfiguration;
+    final static private PwmLogger LOGGER = PwmLogger.forClass(LdapBrowser.class);
+    final StoredConfigurationImpl storedConfiguration;
 
     private Map<String,ChaiProvider> providerCache = new HashMap<>();
 
-    public LdapBrowser(StoredConfiguration storedConfiguration) throws PwmUnrecoverableException {
+    public LdapBrowser(StoredConfigurationImpl storedConfiguration) throws PwmUnrecoverableException {
         this.storedConfiguration = storedConfiguration;
     }
 
@@ -52,6 +56,8 @@ public class LdapBrowser {
             return doBrowseImpl(figureLdapProfileID(profile), dn);
         } catch (ChaiUnavailableException | ChaiOperationException e) {
             throw PwmUnrecoverableException.fromChaiException(e);
+        } catch (Exception e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_LDAP_DATA_ERROR,e.getMessage()));
         }
     }
 
@@ -131,7 +137,9 @@ public class LdapBrowser {
             final String profile,
             final String dn
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, ChaiOperationException {
+            throws ChaiUnavailableException, PwmUnrecoverableException, ChaiOperationException
+    {
+
         final HashMap<String, Boolean> returnMap = new HashMap<>();
         final ChaiProvider chaiProvider = getChaiProvider(profile);
         if ((dn == null || dn.isEmpty()) && chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
@@ -152,7 +160,7 @@ public class LdapBrowser {
 
             }
             for (final String resultDN : results.keySet()) {
-                boolean hasSubs;
+                boolean hasSubs = false;
                 if (results.get(resultDN).containsKey("subordinateCount")) { // only eDir actually returns this operational attribute
                     Integer subordinateCount = Integer.parseInt(results.get(resultDN).get("subordinateCount").iterator().next());
                     hasSubs = subordinateCount > 0;
@@ -162,8 +170,12 @@ public class LdapBrowser {
                     searchHelper.setMaxResults(1);
                     searchHelper.setAttributes(Collections.<String>emptyList());
                     searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.ONE);
-                    final Map<String, Map<String, String>> subSearchResults = chaiProvider.search(resultDN, searchHelper);
-                    hasSubs = !subSearchResults.isEmpty();
+                    try {
+                        final Map<String, Map<String, String>> subSearchResults = chaiProvider.search(resultDN, searchHelper);
+                        hasSubs = !subSearchResults.isEmpty();
+                    } catch (Exception e) {
+                        LOGGER.debug("error during subordinate entry count of " + dn + ", error: " + e.getMessage());
+                    }
                 }
                 returnMap.put(resultDN, hasSubs);
             }

+ 3 - 0
pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java

@@ -379,6 +379,7 @@ public class LdapOperationsHelper {
         final String idleTimeoutMsString = config.readAppProperty(AppProperty.LDAP_CONNECTION_TIMEOUT);
         chaiConfig.setSetting(ChaiSetting.LDAP_CONNECT_TIMEOUT,idleTimeoutMsString);
 
+        /*
         // set the watchdog idle timeout.
         final int idleTimeoutMs = (int)config.readSettingAsLong(PwmSetting.LDAP_IDLE_TIMEOUT) * 1000;
         if (idleTimeoutMs > 0) {
@@ -388,6 +389,8 @@ public class LdapOperationsHelper {
         } else {
             chaiConfig.setSetting(ChaiSetting.WATCHDOG_ENABLE, "false");
         }
+        */
+        chaiConfig.setSetting(ChaiSetting.WATCHDOG_ENABLE, "false");
 
         if (config.readSettingAsBoolean(PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET)) {
             chaiConfig.setSetting(ChaiSetting.AD_SET_POLICY_HINTS_ON_PW_SET,"true");

+ 4 - 2
pwm/servlet/src/password/pwm/ldap/UserSearchEngine.java

@@ -374,7 +374,8 @@ public class UserSearchEngine {
         searchHelper.setTimeLimit((int)timeoutMs);
         final int searchID = searchCounter++;
 
-        final String debugInfo = "searchID=" + searchID + " profile=" + ldapProfile.getIdentifier() + " base=" + context + " filter=" + searchHelper.toString();
+        final String debugInfo = "searchID=" + searchID + " profile=" + ldapProfile.getIdentifier() + " base=" + context
+                + " filter=" + searchHelper.toString() + " maxCount=" + searchHelper.getMaxResults();
         LOGGER.debug(sessionLabel, "performing ldap search for user; " + debugInfo);
 
         final Date startTime = new Date();
@@ -384,7 +385,8 @@ public class UserSearchEngine {
         } catch (ChaiUnavailableException e) {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,e.getMessage()));
         } catch (ChaiOperationException e) {
-            throw new PwmOperationalException(PwmError.forChaiError(e.getErrorCode()),"ldap error during search: " + e.getMessage());
+            throw new PwmOperationalException(PwmError.forChaiError(e.getErrorCode()),"ldap error during searchID="
+                    + searchID + ", error=" + e.getMessage());
         }
         final TimeDuration searchDuration = TimeDuration.fromCurrent(startTime);
 

+ 1 - 1
pwm/servlet/src/password/pwm/ldap/auth/SessionAuthenticator.java

@@ -282,7 +282,7 @@ public class SessionAuthenticator {
 
         //clear permission cache - needs rechecking after login
         LOGGER.debug(pwmSession,"clearing permission cache");
-        pwmSession.getLoginInfoBean().clearPermissions();
+        pwmSession.getUserSessionDataCacheBean().clearPermissions();
 
     }
 }

+ 22 - 2
pwm/servlet/src/password/pwm/util/TimeDuration.java

@@ -313,9 +313,29 @@ public class TimeDuration implements Comparable, Serializable {
             if (sb.length() > 0) {
                 sb.append(", ");
             }
-            sb.append(timeDetail.seconds);
+            if (sb.length() == 0) {
+                if (ms < 5000) {
+                    sb.append(new BigDecimal(ms).movePointLeft(3).stripTrailingZeros());
+
+                    if (ms > 1000) {
+                        sb.deleteCharAt(sb.length()-1);
+                    }
+
+                    if (ms > 2000) {
+                        sb.deleteCharAt(sb.length()-1);
+                    }
+
+                } else {
+                    sb.append(timeDetail.seconds);
+                }
+            } else {
+                sb.append(timeDetail.seconds);
+            }
             sb.append(" ");
-            sb.append(timeDetail.seconds == 1 ? LocaleHelper.getLocalizedMessage(locale,Display.Display_Second,null) : LocaleHelper.getLocalizedMessage(locale,Display.Display_Seconds,null));
+            sb.append(ms == 1000
+                    ? LocaleHelper.getLocalizedMessage(locale,Display.Display_Second,null)
+                    : LocaleHelper.getLocalizedMessage(locale,Display.Display_Seconds,null)
+            );
         }
 
         return sb.toString();

+ 1 - 1
pwm/servlet/src/password/pwm/util/X509Utils.java

@@ -189,7 +189,7 @@ public abstract class X509Utils {
                     final String errorMsg = "server certificate {subject=" + loopCert.getSubjectDN().getName() + "} does not match a certificate in the configuration trust store.";
                     throw new CertificateException(errorMsg);
                 }
-                LOGGER.trace("trusting configured certificate: " + makeDebugText(loopCert));
+                //LOGGER.trace("trusting configured certificate: " + makeDebugText(loopCert));
             }
         }
 

+ 1 - 1
pwm/servlet/src/password/pwm/util/cli/CliEnvironment.java

@@ -24,7 +24,7 @@ package password.pwm.util.cli;
 
 import password.pwm.PwmApplication;
 import password.pwm.config.Configuration;
-import password.pwm.config.ConfigurationReader;
+import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.util.localdb.LocalDB;
 
 import java.io.File;

+ 5 - 5
pwm/servlet/src/password/pwm/util/cli/ConfigLockCommand.java

@@ -23,21 +23,21 @@
 package password.pwm.util.cli;
 
 import password.pwm.PwmConstants;
-import password.pwm.config.ConfigurationReader;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
 
 public class ConfigLockCommand extends AbstractCliCommand {
     public void doCommand()
             throws Exception
     {
         final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
-        if (!Boolean.parseBoolean(storedConfiguration.readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE))) {
+        final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration();
+        if (!Boolean.parseBoolean(storedConfiguration.readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE))) {
             out("configuration is already locked");
             return;
         }
 
-        storedConfiguration.writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE,Boolean.toString(false));
+        storedConfiguration.writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE,Boolean.toString(false));
         configurationReader.saveConfiguration(storedConfiguration, cliEnvironment.getPwmApplication(), PwmConstants.CLI_SESSION_LABEL);
         out("success");
     }

+ 4 - 4
pwm/servlet/src/password/pwm/util/cli/ConfigNewCommand.java

@@ -22,7 +22,7 @@
 
 package password.pwm.util.cli;
 
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -32,12 +32,12 @@ public class ConfigNewCommand extends AbstractCliCommand {
     public void doCommand()
             throws Exception
     {
-        final StoredConfiguration storedConfiguration = StoredConfiguration.newStoredConfiguration();
+        final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.newStoredConfiguration();
         storedConfiguration.initNewRandomSecurityKey();
         storedConfiguration.writeConfigProperty(
-                StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE, Boolean.toString(true));
+                StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE, Boolean.toString(true));
         storedConfiguration.writeConfigProperty(
-                StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_EPOCH, String.valueOf(0));
+                StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_EPOCH, String.valueOf(0));
 
         final File outputFile = (File)cliEnvironment.getOptions().get(CliParameters.REQUIRED_NEW_FILE.getName());
         storedConfiguration.toXml(new FileOutputStream(outputFile, false));

+ 3 - 3
pwm/servlet/src/password/pwm/util/cli/ConfigSetPasswordCommand.java

@@ -23,8 +23,8 @@
 package password.pwm.util.cli;
 
 import password.pwm.PwmConstants;
-import password.pwm.config.ConfigurationReader;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
 
 import java.util.Collections;
 
@@ -35,7 +35,7 @@ public class ConfigSetPasswordCommand extends AbstractCliCommand {
             throws Exception
     {
         final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration();
         final String password;
         if (cliEnvironment.getOptions().containsKey(PASSWORD_OPTIONNAME)) {
             password = (String)cliEnvironment.getOptions().get(PASSWORD_OPTIONNAME);

+ 5 - 5
pwm/servlet/src/password/pwm/util/cli/ConfigUnlockCommand.java

@@ -23,21 +23,21 @@
 package password.pwm.util.cli;
 
 import password.pwm.PwmConstants;
-import password.pwm.config.ConfigurationReader;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
 
 public class ConfigUnlockCommand extends AbstractCliCommand {
     public void doCommand()
             throws Exception
     {
         final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
-        if (Boolean.parseBoolean(storedConfiguration.readConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE))) {
+        final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration();
+        if (Boolean.parseBoolean(storedConfiguration.readConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE))) {
             out("configuration is already unlocked");
             return;
         }
         
-        storedConfiguration.writeConfigProperty(StoredConfiguration.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE,Boolean.toString(true));
+        storedConfiguration.writeConfigProperty(StoredConfigurationImpl.ConfigProperty.PROPERTY_KEY_CONFIG_IS_EDITABLE,Boolean.toString(true));
         configurationReader.saveConfiguration(storedConfiguration, cliEnvironment.getPwmApplication(), PwmConstants.CLI_SESSION_LABEL);
         out("success");
     }

+ 1 - 1
pwm/servlet/src/password/pwm/util/cli/MainClass.java

@@ -30,8 +30,8 @@ import org.apache.log4j.varia.NullAppender;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
-import password.pwm.config.ConfigurationReader;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.Helper;
 import password.pwm.util.localdb.LocalDB;

+ 3 - 3
pwm/servlet/src/password/pwm/util/cli/TokenInfoCommand.java

@@ -24,8 +24,8 @@ package password.pwm.util.cli;
 
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.config.ConfigurationReader;
-import password.pwm.config.StoredConfiguration;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.token.TokenPayload;
 import password.pwm.token.TokenService;
 import password.pwm.util.Helper;
@@ -41,7 +41,7 @@ public class TokenInfoCommand extends AbstractCliCommand {
             throws Exception
     {
         final ConfigurationReader configurationReader = new ConfigurationReader(new File(PwmConstants.DEFAULT_CONFIG_FILE_FILENAME));
-        final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration();
 
 
         final String tokenKey;

+ 112 - 33
pwm/servlet/src/password/pwm/util/localdb/Berkeley_LocalDB.java

@@ -35,6 +35,8 @@ import password.pwm.util.logging.PwmLogger;
 import java.io.File;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import static password.pwm.util.localdb.LocalDB.DB;
 
@@ -58,10 +60,13 @@ public class Berkeley_LocalDB implements LocalDBProvider {
     private final Map<DB, Database> cachedDatabases = new ConcurrentHashMap<>();
 
     // cache of dbIterators
-    private final Set<LocalDB.LocalDBIterator<String>> dbIterators = Collections.newSetFromMap(new ConcurrentHashMap<LocalDB.LocalDBIterator<String>,Boolean>());
+    private final Set<BerkeleyDbIterator<String>> dbIterators = Collections.newSetFromMap(new ConcurrentHashMap<BerkeleyDbIterator<String>,Boolean>());
 
     private LocalDB.Status status = LocalDB.Status.NEW;
 
+    // lock used only for structural changes (like truncate);
+    private Map<DB, ReadWriteLock> lockMap = new ConcurrentHashMap<>();
+
     private boolean readOnly;
 
 // -------------------------- STATIC METHODS --------------------------
@@ -127,36 +132,54 @@ public class Berkeley_LocalDB implements LocalDBProvider {
     public void close()
             throws LocalDBException {
         LOGGER.debug("LocalDB closing....");
-        status = LocalDB.Status.CLOSED;
 
-        for (final DB key : cachedDatabases.keySet()) {
-            try {
-                cachedDatabases.get(key).close();
-            } catch (DatabaseException e) {
-                LOGGER.error("error while closing database " + key.toString() + ": " + e.getMessage());
+        try {
+            for (final ReadWriteLock readWriteLock : lockMap.values()) {
+                readWriteLock.writeLock().lock();
             }
-        }
 
-        cachedDatabases.clear();
-        cachedMaps.clear();
-        final long startTime = System.currentTimeMillis();
+            status = LocalDB.Status.CLOSED;
 
-        boolean closed = false;
-        while (!closed && (System.currentTimeMillis() - startTime) < CLOSE_RETRY_SECONDS * 1000) {
-            try {
-                for (final Database database : cachedDatabases.values()) {
-                    database.close();
+            for (final BerkeleyDbIterator localDBIterator : dbIterators) {
+                LOGGER.trace("closing outstanding iterator for db " + localDBIterator.getDb() + " due to truncate command");
+                localDBIterator.close();
+            }
+
+
+            for (final DB key : cachedDatabases.keySet()) {
+                try {
+                    cachedDatabases.get(key).close();
+                } catch (Throwable e) {
+                    LOGGER.error("error while closing database " + key.toString() + ": " + e.getMessage());
+                }
+            }
+
+            cachedDatabases.clear();
+            cachedMaps.clear();
+            final long startTime = System.currentTimeMillis();
+
+            boolean closed = false;
+            while (!closed && (System.currentTimeMillis() - startTime) < CLOSE_RETRY_SECONDS * 1000) {
+                try {
+                    for (final Database database : cachedDatabases.values()) {
+                        database.close();
+                    }
+                    environment.close();
+                    closed = true;
+                } catch (Exception e) {
+                    LOGGER.error("error while closing environment (will retry for " + CLOSE_RETRY_SECONDS + " seconds): " + e.getMessage());
+                    Helper.pause(5 * 1000);
                 }
-                environment.close();
-                closed = true;
-            } catch (Exception e) {
-                LOGGER.error("error while closing environment (will retry for " + CLOSE_RETRY_SECONDS + " seconds): " + e.getMessage());
-                Helper.pause(5 * 1000);
             }
-        }
 
-        final TimeDuration td = new TimeDuration(System.currentTimeMillis() - startTime);
-        LOGGER.info("closed (" + td.asCompactString() + ")");
+            final TimeDuration td = new TimeDuration(System.currentTimeMillis() - startTime);
+            LOGGER.info("closed (" + td.asCompactString() + ")");
+        } finally {
+            for (final ReadWriteLock readWriteLock : lockMap.values()) {
+                readWriteLock.writeLock().unlock();
+            }
+
+        }
     }
 
     public LocalDB.Status getStatus() {
@@ -167,10 +190,13 @@ public class Berkeley_LocalDB implements LocalDBProvider {
             throws LocalDBException {
         preCheck(false);
         try {
+            lockMap.get(db).readLock().lock();
             return cachedMaps.get(db).containsKey(key);
         } catch (RuntimeExceptionWrapper e) {
             LOGGER.error("error during contains check: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
@@ -178,10 +204,13 @@ public class Berkeley_LocalDB implements LocalDBProvider {
             throws LocalDBException {
         preCheck(false);
         try {
+            lockMap.get(db).readLock().lock();
             return cachedMaps.get(db).get(key);
         } catch (RuntimeExceptionWrapper e) {
             LOGGER.error("error during contains check: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
@@ -197,6 +226,7 @@ public class Berkeley_LocalDB implements LocalDBProvider {
                 final Database database = openDatabase(db, environment, readOnly);
                 cachedDatabases.put(db, database);
                 cachedMaps.put(db, openStoredMap(database));
+                lockMap.put(db, new ReentrantReadWriteLock());
                 LOGGER.trace("database '" + db.toString() + "' open");
             }
         } catch (DatabaseException e) {
@@ -211,15 +241,18 @@ public class Berkeley_LocalDB implements LocalDBProvider {
     {
         preCheck(false);
         try {
+            lockMap.get(db).readLock().lock();
             if (dbIterators.size() > ITERATOR_LIMIT) {
                 throw new LocalDBException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"over " + ITERATOR_LIMIT + " iterators are outstanding, maximum limit exceeded"));
             }
-            final LocalDB.LocalDBIterator<String> iterator = new DbIterator<String>(db);
+            final BerkeleyDbIterator<String> iterator = new BerkeleyDbIterator<>(db);
             dbIterators.add(iterator);
             LOGGER.trace(this.getClass().getSimpleName() + " issued iterator for " + db.toString() + ", outstanding iterators: " + dbIterators.size());
             return iterator;
         } catch (Exception e) {
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
@@ -228,10 +261,13 @@ public class Berkeley_LocalDB implements LocalDBProvider {
         preCheck(true);
 
         try {
+            lockMap.get(db).readLock().lock();
             cachedMaps.get(db).putAll(keyValueMap);
         } catch (RuntimeExceptionWrapper e) {
             LOGGER.error("error during multiple-put: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
@@ -240,34 +276,44 @@ public class Berkeley_LocalDB implements LocalDBProvider {
         preCheck(true);
 
         try {
+            lockMap.get(db).readLock().lock();
             final StoredMap<String, String> transactionDB = cachedMaps.get(db);
             return null != transactionDB.put(key, value);
         } catch (RuntimeExceptionWrapper e) {
             LOGGER.error("error during put: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
     public boolean remove(final DB db, final String key)
-            throws LocalDBException {
+            throws LocalDBException
+    {
         preCheck(true);
         try {
+            lockMap.get(db).readLock().lock();
             return cachedMaps.get(db).keySet().remove(key);
         } catch (RuntimeExceptionWrapper e) {
             LOGGER.error("error during remove: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
     public void removeAll(final DB db, final Collection<String> keys)
-
-            throws LocalDBException {
+            throws LocalDBException
+    {
         preCheck(true);
         try {
+            lockMap.get(db).readLock().lock();
             cachedMaps.get(db).keySet().removeAll(keys);
         } catch (RuntimeExceptionWrapper e) {
             LOGGER.error("error during removeAll: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
@@ -275,19 +321,32 @@ public class Berkeley_LocalDB implements LocalDBProvider {
             throws LocalDBException {
         preCheck(false);
         try {
+            lockMap.get(db).readLock().lock();
             final StoredMap<String, String> dbMap = cachedMaps.get(db);
             assert dbMap != null;
             return dbMap.size();
         } catch (RuntimeExceptionWrapper e) {
             LOGGER.error("error during size: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).readLock().unlock();
         }
     }
 
     public void truncate(final DB db)
             throws LocalDBException {
         preCheck(true);
+        LOGGER.trace("beginning truncate of db " + db.toString());
+        final Date startTime = new Date();
         try {
+            for (final LocalDB.LocalDBIterator localDBIterator : dbIterators) {
+                if (((BerkeleyDbIterator)localDBIterator).getDb() == db) {
+                    LOGGER.trace("closing outstanding iterator for db " + db + " due to truncate command");
+                    localDBIterator.close();
+                }
+            }
+
+            lockMap.get(db).writeLock().lock();
             cachedMaps.remove(db);
             cachedDatabases.remove(db).close();
 
@@ -296,40 +355,60 @@ public class Berkeley_LocalDB implements LocalDBProvider {
             final Database database = openDatabase(db, environment, readOnly);
             cachedDatabases.put(db, database);
             cachedMaps.put(db, openStoredMap(database));
+            LOGGER.trace("completed truncate of db " + db.toString() + " in " + TimeDuration.fromCurrent(startTime).asCompactString());
         } catch (DatabaseException e) {
             LOGGER.error("error during truncate: " + e.toString());
             throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } catch (Exception e) {
+            LOGGER.error("unexpected error during truncate: " + e.toString(),e);
+            throw new LocalDBException(new ErrorInformation(PwmError.ERROR_LOCALDB_UNAVAILABLE,e.toString()));
+        } finally {
+            lockMap.get(db).writeLock().unlock();
         }
     }
 
 // -------------------------- INNER CLASSES --------------------------
 
-    private class DbIterator<K> implements LocalDB.LocalDBIterator<String> {
+    private class BerkeleyDbIterator<K> implements LocalDB.LocalDBIterator<String> {
         private DB db;
         private Iterator<String> innerIter;
 
-        private DbIterator(final DB db) throws DatabaseException {
+        private BerkeleyDbIterator(final DB db) throws DatabaseException {
             this.db = db;
-            this.innerIter = cachedMaps.get(db).keySet().<Iterator>iterator();
+            this.innerIter = cachedMaps.get(db).keySet().<K>iterator();
         }
 
         public boolean hasNext() {
-            return innerIter.hasNext();
+            return innerIter != null && innerIter.hasNext();
         }
 
         public void close() {
+            Iterator copiedIterator = innerIter;
             innerIter = null;
+            if (copiedIterator != null) {
+                final Date startTime = new Date();
+                int cycleCount = 0;
+                while (copiedIterator.hasNext()) {
+                    cycleCount++;
+                    copiedIterator.next();
+                }
+                LOGGER.trace("closed iterator for " + this.getDb() + " with " + cycleCount + " unused cycles in " + TimeDuration.fromCurrent(startTime).asCompactString());
+            }
             dbIterators.remove(this);
             LOGGER.trace(this.getClass().getSimpleName() + " closed iterator for " + db.toString() + ", outstanding iterators: " + dbIterators.size());
         }
 
         public String next() {
-            return innerIter.next();
+            return innerIter != null ? innerIter.next() : null;
         }
 
         public void remove() {
             throw new UnsupportedOperationException("Berkeley LocalDB iterator does not support removals");
         }
+
+        public DB getDb() {
+            return db;
+        }
     }
 
     public File getFileLocation() {

+ 45 - 0
pwm/servlet/src/password/pwm/util/macro/StandardMacros.java

@@ -67,6 +67,7 @@ public abstract class StandardMacros {
         defaultMacros.add(SiteURLMacro.class);
         defaultMacros.add(SiteHostMacro.class);
         defaultMacros.add(RandomCharMacro.class);
+        defaultMacros.add(RandomNumberMacro.class);
         defaultMacros.add(UUIDMacro.class);
         defaultMacros.add(UserLdapProfileMacro.class);
         STANDARD_MACROS = Collections.unmodifiableList(defaultMacros);
@@ -508,6 +509,50 @@ public abstract class StandardMacros {
         }
     }
 
+    public static class RandomNumberMacro extends AbstractMacro {
+        private static final Pattern PATTERN = Pattern.compile("@RandomNumber(:[^@]*)?@");
+
+        public Pattern getRegExPattern() {
+            return PATTERN;
+        }
+
+        public String replaceValue(
+                final String matchValue,
+                final MacroRequestInfo macroRequestInfo
+        )
+                throws MacroParseException
+        {
+            if (matchValue == null || matchValue.length() < 1) {
+                return "";
+            }
+
+            final List<String> parameters = splitMacroParameters(matchValue,"RandomNumber");
+            if (parameters.size() != 2) {
+                throw new MacroParseException("incorrect number of parameter of RandomNumber: "
+                        + parameters.size() + ", should be 2");
+            }
+
+            final int min, max;
+            try {
+                min = Integer.parseInt(parameters.get(0));
+            } catch (NumberFormatException e) {
+                throw new MacroParseException("error parsing minimum value parameter of RandomNumber: " + e.getMessage());
+            }
+
+            try {
+                max = Integer.parseInt(parameters.get(1));
+            } catch (NumberFormatException e) {
+                throw new MacroParseException("error parsing maximum value parameter of RandomNumber: " + e.getMessage());
+            }
+
+            if (min > max) {
+                throw new MacroParseException("minimum value is less than maximum value parameter of RandomNumber");
+            }
+
+            final int range = max - min;
+            return String.valueOf(PwmRandom.getInstance().nextInt(range) + min);
+        }
+    }
     public static class UUIDMacro extends AbstractMacro {
         private static final Pattern PATTERN = Pattern.compile("@UUID@");
 

+ 1 - 1
pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java

@@ -558,7 +558,7 @@ public class PasswordUtility {
     private static void invokePostChangePasswordActions(final PwmSession pwmSession, final String newPassword)
             throws PwmUnrecoverableException
     {
-        final List<PostChangePasswordAction> postChangePasswordActions = pwmSession.getLoginInfoBean().removePostChangePasswordActions();
+        final List<PostChangePasswordAction> postChangePasswordActions = pwmSession.getUserSessionDataCacheBean().removePostChangePasswordActions();
         if (postChangePasswordActions == null || postChangePasswordActions.isEmpty()) {
             LOGGER.trace(pwmSession, "no post change password actions pending from previous operations");
             return;

+ 36 - 16
pwm/servlet/src/password/pwm/util/report/ReportService.java

@@ -136,9 +136,9 @@ public class ReportService implements PwmService {
 
         timer = new Timer();
 
-        final long nextZuluZeroTime = Helper.nextZuluZeroTime().getTime();
+        final Date nextZuluZeroTime = Helper.nextZuluZeroTime();
         if (settings.getJobOffsetSeconds() >= 0) {
-            final long nextScheduleTime = nextZuluZeroTime + (settings.getJobOffsetSeconds() * 1000);
+            final long nextScheduleTime = nextZuluZeroTime.getTime() + (settings.getJobOffsetSeconds() * 1000);
             timer.scheduleAtFixedRate(new DredgeTask(),new Date(nextScheduleTime), TimeDuration.DAY.getTotalMilliseconds());
         }
 
@@ -148,7 +148,7 @@ public class ReportService implements PwmService {
         }
         LOGGER.debug(startupMsg);
         timer.schedule(new RolloverTask(), 1);
-        timer.scheduleAtFixedRate(new RolloverTask(),new Date(nextZuluZeroTime), TimeDuration.DAY.getTotalMilliseconds());
+        timer.scheduleAtFixedRate(new RolloverTask(), nextZuluZeroTime, TimeDuration.DAY.getTotalMilliseconds());
 
         status = STATUS.OPEN;
     }
@@ -227,7 +227,7 @@ public class ReportService implements PwmService {
     private void updateCacheFromLdap()
             throws ChaiUnavailableException, ChaiOperationException, PwmOperationalException, PwmUnrecoverableException
     {
-        LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"beginning process to updating user cache records from ldap");
+        LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL, "beginning process to updating user cache records from ldap");
         if (status != STATUS.OPEN) {
             return;
         }
@@ -276,22 +276,42 @@ public class ReportService implements PwmService {
 
     private void updateRestingCacheData() {
         final long startTime = System.currentTimeMillis();
-        LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,"beginning cache review process");
-        final ClosableIterator<UserCacheRecord> iterator = iterator();
+        final Timer timer = new Timer(Helper.makeThreadName(this.pwmApplication,ReportService.class) + " - cache review process");
+        final Map<Integer,Integer> examinedRecordHolder = new HashMap<>(); // needed for inner class access, its just for debug log so concurrency not required
+        examinedRecordHolder.put(0, 0);
         int examinedRecords = 0;
-        while (iterator.hasNext() && status == STATUS.OPEN) {
-            final UserCacheRecord record = iterator.next(); // (purge routine is embedded in next();
+        ClosableIterator<UserCacheRecord> iterator = null;
+        try {
+            iterator = iterator();
+            timer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    final TimeDuration progressDuration = TimeDuration.fromCurrent(startTime);
+                    final int count = examinedRecordHolder.get(0);
+                    LOGGER.trace(PwmConstants.REPORTING_SESSION_LABEL,"cache review process in progress, examined "
+                            + count + " records in " + progressDuration.asCompactString());
+                }
+            }, 30 * 1000, 30 * 1000);
+            LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL, "beginning cache review process");
+            while (iterator.hasNext() && status == STATUS.OPEN) {
+                final UserCacheRecord record = iterator.next(); // (purge routine is embedded in next();
 
-            if (summaryData != null && record != null) {
-                summaryData.update(record);
-            }
+                if (summaryData != null && record != null) {
+                    summaryData.update(record);
+                }
 
-            examinedRecords++;
+                examinedRecords++;
+                examinedRecordHolder.put(0, examinedRecords);
+            }
+            final TimeDuration totalTime = TimeDuration.fromCurrent(startTime);
+            LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,
+                    "completed cache review process of " + examinedRecords + " cached report records in " + totalTime.asCompactString());
+        } finally {
+            timer.cancel();
+            if (iterator != null) {
+                iterator.close();
+            }
         }
-        iterator.close();
-        final TimeDuration totalTime = TimeDuration.fromCurrent(startTime);
-        LOGGER.debug(PwmConstants.REPORTING_SESSION_LABEL,
-                "completed cache review process of " + examinedRecords + " cached report records in " + totalTime.asCompactString());
     }
 
     public boolean updateCache(final UserInfoBean uiBean)

+ 1 - 1
pwm/servlet/src/password/pwm/util/report/UserCacheService.java

@@ -237,7 +237,7 @@ public class UserCacheService implements PwmService {
             return localDB.remove(DB,key.getKey());
         }
 
-        private void clear()
+        private void    clear()
                 throws LocalDBException
         {
             localDB.truncate(DB);

+ 96 - 0
pwm/servlet/src/password/pwm/util/secure/ChecksumInputStream.java

@@ -0,0 +1,96 @@
+package password.pwm.util.secure;
+
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class ChecksumInputStream extends InputStream {
+    private final MessageDigest messageDigest;
+    private final InputStream wrappedStream;
+
+    public ChecksumInputStream(PwmHashAlgorithm hash, InputStream wrappedStream) throws PwmUnrecoverableException {
+        this.wrappedStream = wrappedStream;
+
+        try {
+            messageDigest = MessageDigest.getInstance(hash.getAlgName());
+        } catch (NoSuchAlgorithmException e) {
+            final String errorMsg = "missing hash algorithm: " + e.getMessage();
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CRYPT_ERROR, errorMsg);
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+    }
+
+    @Override
+    public int read() throws IOException {
+        final int value = wrappedStream.read();
+        if (value >= 0) {
+            messageDigest.update((byte)value);
+        }
+        return value;
+    }
+
+    @Override
+    public int read(final byte[] b) throws IOException {
+        final int length = wrappedStream.read(b);
+        if (length > 0) {
+            messageDigest.update(b,0,length);
+        }
+        return length;
+    }
+
+    @Override
+    public int read(final byte[] b, final int off, final int len) throws IOException {
+        int length = wrappedStream.read(b, off, len);
+        if (length > 0) {
+            messageDigest.update(b,off,length);
+        }
+        return length;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        throw new IOException("operation not supported");
+    }
+
+    @Override
+    public int available() throws IOException {
+        return wrappedStream.available();
+    }
+
+    @Override
+    public void close() throws IOException {
+        wrappedStream.close();
+    }
+
+    @Override
+    public synchronized void mark(int readlimit) {
+        wrappedStream.mark(readlimit);
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        throw new IOException("operation not supported");
+    }
+
+    @Override
+    public boolean markSupported() {
+        return false;
+    }
+
+    public byte[] getInProgressChecksum() {
+        return messageDigest.digest();
+    }
+
+    public byte[] closeAndFinalChecksum() throws IOException {
+        final byte[] buffer = new byte[1024];
+
+        while (read(buffer) > 0); // read out the remainder of the stream contents
+
+        return getInProgressChecksum();
+    }
+}

+ 28 - 0
pwm/servlet/src/password/pwm/util/secure/HmacAlgorithm.java

@@ -0,0 +1,28 @@
+package password.pwm.util.secure;
+
+enum HmacAlgorithm {
+    HMAC_SHA_256("HmacSHA256", PwmSecurityKey.Type.HMAC_256, 32),
+    HMAC_SHA_512("HmacSHA512", PwmSecurityKey.Type.HMAC_512, 64),;
+
+    private final String algorithmName;
+    private final PwmSecurityKey.Type keyType;
+    private final int length;
+
+    HmacAlgorithm(String algorithmName, PwmSecurityKey.Type keyType, int length) {
+        this.algorithmName = algorithmName;
+        this.keyType = keyType;
+        this.length = length;
+    }
+
+    public String getAlgorithmName() {
+        return algorithmName;
+    }
+
+    public PwmSecurityKey.Type getKeyType() {
+        return keyType;
+    }
+
+    public int getLength() {
+        return length;
+    }
+}

+ 5 - 5
pwm/servlet/src/password/pwm/util/secure/PwmBlockAlgorithm.java

@@ -40,7 +40,7 @@ public enum PwmBlockAlgorithm {
     AES128_HMAC256(
             "AES",
             PwmSecurityKey.Type.AES,
-            SecureEngine.HmacAlgorithm.HMAC_SHA_256,
+            HmacAlgorithm.HMAC_SHA_256,
             "PWM.AES128_HMAC256".getBytes(PwmConstants.DEFAULT_CHARSET),
             "AES128+Hmac256"
     ),
@@ -48,7 +48,7 @@ public enum PwmBlockAlgorithm {
     AES256_HMAC512(
             "AES",
             PwmSecurityKey.Type.AES_256,
-            SecureEngine.HmacAlgorithm.HMAC_SHA_512,
+            HmacAlgorithm.HMAC_SHA_512,
             "PWM.AES256_HMAC512".getBytes(PwmConstants.DEFAULT_CHARSET),
             "AES256+Hmac512"
     ),
@@ -70,7 +70,7 @@ public enum PwmBlockAlgorithm {
     private final PwmSecurityKey.Type blockKey;
 
     /** HMAC algorithm needed for this item, if any. */
-    private final SecureEngine.HmacAlgorithm hmacAlgorithm;
+    private final HmacAlgorithm hmacAlgorithm;
 
     /**
      * Prefix that is prepended to encryption output methods and expected to be prepended to input methods.  The prefix is not itself secured and must be treated
@@ -82,7 +82,7 @@ public enum PwmBlockAlgorithm {
      */
     private final String label;
 
-    PwmBlockAlgorithm(String algName, PwmSecurityKey.Type blockKey, SecureEngine.HmacAlgorithm hmacAlgorithm, byte[] prefix, String label) {
+    PwmBlockAlgorithm(String algName, PwmSecurityKey.Type blockKey, HmacAlgorithm hmacAlgorithm, byte[] prefix, String label) {
         this.algName = algName;
         this.blockKey = blockKey;
         this.hmacAlgorithm = hmacAlgorithm;
@@ -98,7 +98,7 @@ public enum PwmBlockAlgorithm {
         return blockKey;
     }
 
-    SecureEngine.HmacAlgorithm getHmacAlgorithm() {
+    HmacAlgorithm getHmacAlgorithm() {
         return hmacAlgorithm;
     }
 

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott