瀏覽代碼

immutable storedconfigimpl

Jason Rivard 5 年之前
父節點
當前提交
7ceb429723
共有 75 個文件被更改,包括 1037 次插入1096 次删除
  1. 31 6
      server/src/main/java/password/pwm/PwmApplication.java
  2. 0 4
      server/src/main/java/password/pwm/PwmConstants.java
  3. 4 3
      server/src/main/java/password/pwm/PwmEnvironment.java
  4. 10 43
      server/src/main/java/password/pwm/config/Configuration.java
  5. 2 2
      server/src/main/java/password/pwm/config/SettingUIFunction.java
  6. 1 2
      server/src/main/java/password/pwm/config/StoredValue.java
  7. 14 8
      server/src/main/java/password/pwm/config/function/AbstractUriCertImportFunction.java
  8. 11 5
      server/src/main/java/password/pwm/config/function/ActionCertImportFunction.java
  9. 4 4
      server/src/main/java/password/pwm/config/function/LdapCertImportFunction.java
  10. 6 4
      server/src/main/java/password/pwm/config/function/OAuthCertImportFunction.java
  11. 7 6
      server/src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java
  12. 5 3
      server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java
  13. 4 4
      server/src/main/java/password/pwm/config/function/SmtpCertImportFunction.java
  14. 5 5
      server/src/main/java/password/pwm/config/function/SyslogCertImportFunction.java
  15. 3 2
      server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java
  16. 0 90
      server/src/main/java/password/pwm/config/stored/ComparingChangeLog.java
  17. 25 22
      server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
  18. 0 1
      server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java
  19. 7 4
      server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
  20. 40 15
      server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java
  21. 6 33
      server/src/main/java/password/pwm/config/stored/StoredConfiguration.java
  22. 40 24
      server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java
  23. 85 345
      server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  24. 266 0
      server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java
  25. 79 111
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  26. 28 6
      server/src/main/java/password/pwm/config/value/AbstractValue.java
  27. 1 1
      server/src/main/java/password/pwm/config/value/BooleanValue.java
  28. 0 13
      server/src/main/java/password/pwm/config/value/FileValue.java
  29. 6 9
      server/src/main/java/password/pwm/config/value/NamedSecretValue.java
  30. 5 10
      server/src/main/java/password/pwm/config/value/PasswordValue.java
  31. 2 2
      server/src/main/java/password/pwm/health/CertificateChecker.java
  32. 14 19
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  33. 43 48
      server/src/main/java/password/pwm/http/ContextManager.java
  34. 2 42
      server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java
  35. 49 36
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  36. 6 15
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java
  37. 7 6
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java
  38. 10 6
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  39. 10 7
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java
  40. 4 4
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java
  41. 13 11
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  42. 5 6
      server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java
  43. 12 5
      server/src/main/java/password/pwm/i18n/PwmLocaleBundle.java
  44. 1 1
      server/src/main/java/password/pwm/svc/node/NodeMachine.java
  45. 1 1
      server/src/main/java/password/pwm/svc/node/StoredNodeData.java
  46. 2 2
      server/src/main/java/password/pwm/svc/report/ReportSettings.java
  47. 1 5
      server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java
  48. 7 7
      server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java
  49. 24 23
      server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java
  50. 4 2
      server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java
  51. 1 1
      server/src/main/java/password/pwm/util/cli/commands/ConfigNewCommand.java
  52. 4 2
      server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java
  53. 4 2
      server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java
  54. 4 2
      server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java
  55. 5 3
      server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java
  56. 3 7
      server/src/main/java/password/pwm/util/java/PwmCallable.java
  57. 4 8
      server/src/main/java/password/pwm/util/java/PwmSupplier.java
  58. 30 3
      server/src/main/java/password/pwm/util/java/StringUtil.java
  59. 15 0
      server/src/main/java/password/pwm/util/logging/PwmLogManager.java
  60. 1 1
      server/src/main/java/password/pwm/util/secure/HmacAlgorithm.java
  61. 6 5
      server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java
  62. 11 1
      server/src/main/java/password/pwm/util/secure/SecureEngine.java
  63. 8 1
      server/src/test/java/password/pwm/config/PwmSettingTest.java
  64. 2 2
      server/src/test/java/password/pwm/config/option/IdentityVerificationMethodEnumTest.java
  65. 1 1
      server/src/test/java/password/pwm/health/HealthMessageTest.java
  66. 1 1
      server/src/test/java/password/pwm/http/client/PwmHttpClientTest.java
  67. 11 11
      server/src/test/java/password/pwm/http/filter/RequestInitializationFilterTest.java
  68. 1 1
      server/src/test/java/password/pwm/i18n/NonLocalizedKeyTest.java
  69. 1 1
      server/src/test/java/password/pwm/svc/event/CEFAuditFormatterTest.java
  70. 1 1
      server/src/test/java/password/pwm/svc/event/JsonAuditFormatterTest.java
  71. 3 3
      server/src/test/java/password/pwm/svc/wordlist/WordlistServiceTest.java
  72. 1 1
      server/src/test/java/password/pwm/util/LDAPPermissionCalculatorTest.java
  73. 1 1
      server/src/test/java/password/pwm/util/localdb/LocalDBLoggerExtendedTest.java
  74. 5 3
      server/src/test/java/password/pwm/util/localdb/TestHelper.java
  75. 1 1
      server/src/test/java/password/pwm/util/macro/MacroTest.java

+ 31 - 6
server/src/main/java/password/pwm/PwmApplication.java

@@ -28,6 +28,8 @@ import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmException;
@@ -101,8 +103,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ExecutorService;
-
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 
 /**
 /**
  * A repository for objects common to the servlet context.  A singleton
  * A repository for objects common to the servlet context.  A singleton
@@ -307,20 +309,18 @@ public class PwmApplication
 
 
         try
         try
         {
         {
-            pwmEnvironment.getConfig().outputToLog();
+            outputConfigurationToLog( this );
         }
         }
         catch ( PwmException e )
         catch ( PwmException e )
         {
         {
             LOGGER.error( "error outputting log to debug: " + e.getMessage() );
             LOGGER.error( "error outputting log to debug: " + e.getMessage() );
         }
         }
-
-
-
+        
         // detect if config has been modified since previous startup
         // detect if config has been modified since previous startup
         try
         try
         {
         {
             final String previousHash = readAppAttribute( AppAttribute.CONFIG_HASH, String.class );
             final String previousHash = readAppAttribute( AppAttribute.CONFIG_HASH, String.class );
-            final String currentHash = pwmEnvironment.getConfig().configurationHash();
+            final String currentHash = pwmEnvironment.getConfig().configurationHash( this.getSecureService() );
             if ( previousHash == null || !previousHash.equals( currentHash ) )
             if ( previousHash == null || !previousHash.equals( currentHash ) )
             {
             {
                 writeAppAttribute( AppAttribute.CONFIG_HASH, currentHash );
                 writeAppAttribute( AppAttribute.CONFIG_HASH, currentHash );
@@ -520,6 +520,31 @@ public class PwmApplication
         }
         }
     }
     }
 
 
+    private static void outputConfigurationToLog( final PwmApplication pwmApplication )
+            throws PwmUnrecoverableException
+    {
+        if ( !LOGGER.isEnabled( PwmLogLevel.TRACE ) )
+        {
+            return;
+        }
+
+        final StoredConfiguration storedConfiguration = pwmApplication.getConfig().getStoredConfiguration();
+        final Map<String, String> debugStrings = StoredConfigurationUtil.makeDebugMap( storedConfiguration, storedConfiguration.modifiedItems(), PwmConstants.DEFAULT_LOCALE );
+        final List<Supplier<CharSequence>> outputStrings = new ArrayList<>();
+
+        for ( final Map.Entry<String, String> entry : debugStrings.entrySet() )
+        {
+            final String spacedValue = entry.getValue().replace( "\n", "\n   " );
+            final String output = " " + entry.getKey() + "\n   " + spacedValue + "\n";
+            outputStrings.add( () -> output );
+        }
+
+        LOGGER.trace( () -> "--begin current configuration output--" );
+        outputStrings.forEach( LOGGER::trace );
+        LOGGER.trace( () -> "--end current configuration output--" );
+    }
+
+
     public String getInstanceID( )
     public String getInstanceID( )
     {
     {
         return instanceID;
         return instanceID;

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

@@ -23,7 +23,6 @@ package password.pwm;
 import org.apache.commons.csv.CSVFormat;
 import org.apache.commons.csv.CSVFormat;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
-import password.pwm.util.secure.PwmHashAlgorithm;
 
 
 import java.io.InputStream;
 import java.io.InputStream;
 import java.net.URL;
 import java.net.URL;
@@ -119,9 +118,6 @@ public abstract class PwmConstants
     public static final String REQUEST_ATTR_FORGOTTEN_PW_AVAIL_TOKEN_DEST_CACHE = "ForgottenPw-AvailableTokenDestCache";
     public static final String REQUEST_ATTR_FORGOTTEN_PW_AVAIL_TOKEN_DEST_CACHE = "ForgottenPw-AvailableTokenDestCache";
     public static final String REQUEST_ATTR_PWM_APPLICATION = "PwmApplication";
     public static final String REQUEST_ATTR_PWM_APPLICATION = "PwmApplication";
 
 
-    public static final PwmHashAlgorithm SETTING_CHECKSUM_HASH_METHOD = PwmHashAlgorithm.SHA256;
-
-
     public static final String LOG_REMOVED_VALUE_REPLACEMENT = readPwmConstantsBundle( "log.removedValue" );
     public static final String LOG_REMOVED_VALUE_REPLACEMENT = readPwmConstantsBundle( "log.removedValue" );
 
 
     public static final Collection<Locale> INCLUDED_LOCALES;
     public static final Collection<Locale> INCLUDED_LOCALES;

+ 4 - 3
server/src/main/java/password/pwm/PwmEnvironment.java

@@ -74,7 +74,8 @@ public class PwmEnvironment
         AppliancePort,
         AppliancePort,
         ApplianceHostnameFile,
         ApplianceHostnameFile,
         ApplianceTokenFile,
         ApplianceTokenFile,
-        InstanceID,;
+        InstanceID,
+        InitConsoleLogLevel,;
 
 
         public static ApplicationParameter forString( final String input )
         public static ApplicationParameter forString( final String input )
         {
         {
@@ -317,7 +318,7 @@ public class PwmEnvironment
             final String rawValue = readValueFromSystem( EnvironmentParameter.applicationParamFile, contextName );
             final String rawValue = readValueFromSystem( EnvironmentParameter.applicationParamFile, contextName );
             if ( rawValue != null )
             if ( rawValue != null )
             {
             {
-                return parseApplicationParamValueParameter( rawValue );
+                return readAppParametersFromPath( rawValue );
             }
             }
             return Collections.emptyMap();
             return Collections.emptyMap();
         }
         }
@@ -393,7 +394,7 @@ public class PwmEnvironment
             return returnFlags;
             return returnFlags;
         }
         }
 
 
-        public static Map<ApplicationParameter, String> parseApplicationParamValueParameter( final String input )
+        public static Map<ApplicationParameter, String> readAppParametersFromPath( final String input )
         {
         {
             if ( input == null )
             if ( input == null )
             {
             {

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

@@ -45,10 +45,8 @@ import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordRule;
 import password.pwm.config.profile.PwmPasswordRule;
 import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
-import password.pwm.config.stored.ComparingChangeLog;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
-import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.CustomLinkValue;
 import password.pwm.config.value.CustomLinkValue;
@@ -80,6 +78,7 @@ import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.SecureService;
 
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.InvocationTargetException;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Certificate;
@@ -97,7 +96,6 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeMap;
-import java.util.function.Supplier;
 
 
 /**
 /**
  * @author Jason D. Rivard
  * @author Jason D. Rivard
@@ -110,8 +108,6 @@ public class Configuration implements SettingReader
 
 
     private DataCache dataCache = new DataCache();
     private DataCache dataCache = new DataCache();
 
 
-    private String cashedConfigurationHash;
-
     public Configuration( final StoredConfiguration storedConfiguration )
     public Configuration( final StoredConfiguration storedConfiguration )
     {
     {
         this.storedConfiguration = storedConfiguration;
         this.storedConfiguration = storedConfiguration;
@@ -127,30 +123,6 @@ public class Configuration implements SettingReader
         }
         }
     }
     }
 
 
-    public void outputToLog( )
-            throws PwmUnrecoverableException
-    {
-        if ( !LOGGER.isEnabled( PwmLogLevel.TRACE ) )
-        {
-            return;
-        }
-
-        final ComparingChangeLog changeLog = ComparingChangeLog.create( StoredConfigurationFactory.newStoredConfiguration(), storedConfiguration );
-        final Map<String, String> debugStrings = StoredConfigurationUtil.asDebugMap( storedConfiguration, changeLog.changedValues(), PwmConstants.DEFAULT_LOCALE );
-        final List<Supplier<CharSequence>> outputStrings = new ArrayList<>();
-
-        for ( final Map.Entry<String, String> entry : debugStrings.entrySet() )
-        {
-            final String spacedValue = entry.getValue().replace( "\n", "\n   " );
-            final String output = " " + entry.getKey() + "\n   " + spacedValue + "\n";
-            outputStrings.add( () -> output );
-        }
-
-        LOGGER.trace( () -> "--begin current configuration output--" );
-        outputStrings.forEach( LOGGER::trace );
-        LOGGER.trace( () -> "--end current configuration output--" );
-    }
-
     public List<FormConfiguration> readSettingAsForm( final PwmSetting setting )
     public List<FormConfiguration> readSettingAsForm( final PwmSetting setting )
     {
     {
         final StoredValue value = readStoredValue( setting );
         final StoredValue value = readStoredValue( setting );
@@ -647,7 +619,7 @@ public class Configuration implements SettingReader
 
 
         // set case sensitivity
         // set case sensitivity
         final String caseSensitivitySetting = JavaTypeConverter.valueToString( storedConfiguration.readSetting(
         final String caseSensitivitySetting = JavaTypeConverter.valueToString( storedConfiguration.readSetting(
-                PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY ) );
+                PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY, null ) );
         if ( !"read".equals( caseSensitivitySetting ) )
         if ( !"read".equals( caseSensitivitySetting ) )
         {
         {
             passwordPolicySettings.put( PwmPasswordRule.CaseSensitive.getKey(), caseSensitivitySetting );
             passwordPolicySettings.put( PwmPasswordRule.CaseSensitive.getKey(), caseSensitivitySetting );
@@ -678,7 +650,7 @@ public class Configuration implements SettingReader
 
 
     public boolean isDefaultValue( final PwmSetting pwmSetting )
     public boolean isDefaultValue( final PwmSetting pwmSetting )
     {
     {
-        return storedConfiguration.isDefaultValue( pwmSetting );
+        return storedConfiguration.isDefaultValue( pwmSetting, null );
     }
     }
 
 
     public Collection<Locale> localesForSetting( final PwmSetting setting )
     public Collection<Locale> localesForSetting( final PwmSetting setting )
@@ -716,7 +688,7 @@ public class Configuration implements SettingReader
 
 
     public Map<FileValue.FileInformation, FileValue.FileContent> readSettingAsFile( final PwmSetting setting )
     public Map<FileValue.FileInformation, FileValue.FileContent> readSettingAsFile( final PwmSetting setting )
     {
     {
-        final FileValue fileValue = ( FileValue ) storedConfiguration.readSetting( setting );
+        final FileValue fileValue = ( FileValue ) storedConfiguration.readSetting( setting, null );
         return ( Map ) fileValue.toNativeObject();
         return ( Map ) fileValue.toNativeObject();
     }
     }
 
 
@@ -1013,7 +985,7 @@ public class Configuration implements SettingReader
             return dataCache.settings.get( setting );
             return dataCache.settings.get( setting );
         }
         }
 
 
-        final StoredValue readValue = storedConfiguration.readSetting( setting );
+        final StoredValue readValue = storedConfiguration.readSetting( setting, null );
         dataCache.settings.put( setting, readValue );
         dataCache.settings.put( setting, readValue );
         return readValue;
         return readValue;
     }
     }
@@ -1127,9 +1099,9 @@ public class Configuration implements SettingReader
         return profileFactory.makeFromStoredConfiguration( storedConfiguration, profileID );
         return profileFactory.makeFromStoredConfiguration( storedConfiguration, profileID );
     }
     }
 
 
-    public StoredConfiguration getStoredConfiguration( ) throws PwmUnrecoverableException
+    public StoredConfiguration getStoredConfiguration( )
     {
     {
-        return this.storedConfiguration.copy();
+        return this.storedConfiguration;
     }
     }
 
 
     public boolean isDevDebugMode( )
     public boolean isDevDebugMode( )
@@ -1137,20 +1109,16 @@ public class Configuration implements SettingReader
         return Boolean.parseBoolean( readAppProperty( AppProperty.LOGGING_DEV_OUTPUT ) );
         return Boolean.parseBoolean( readAppProperty( AppProperty.LOGGING_DEV_OUTPUT ) );
     }
     }
 
 
-    public String configurationHash( )
+    public String configurationHash( final SecureService secureService )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        if ( this.cashedConfigurationHash == null )
-        {
-            this.cashedConfigurationHash = storedConfiguration.settingChecksum();
-        }
-        return cashedConfigurationHash;
+        return storedConfiguration.valueHash();
     }
     }
 
 
     public Set<PwmSetting> nonDefaultSettings( )
     public Set<PwmSetting> nonDefaultSettings( )
     {
     {
         final Set<PwmSetting> returnSet = new LinkedHashSet<>();
         final Set<PwmSetting> returnSet = new LinkedHashSet<>();
-        for ( final StoredConfigItemKey key : this.storedConfiguration.modifiedSettings() )
+        for ( final StoredConfigItemKey key : this.storedConfiguration.modifiedItems() )
         {
         {
             if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             {
             {
@@ -1166,7 +1134,6 @@ public class Configuration implements SettingReader
         return mode == null
         return mode == null
                 ? CertificateMatchingMode.CA_ONLY
                 ? CertificateMatchingMode.CA_ONLY
                 : mode;
                 : mode;
-
     }
     }
 
 
     public Optional<PeopleSearchProfile> getPublicPeopleSearchProfile()
     public Optional<PeopleSearchProfile> getPublicPeopleSearchProfile()

+ 2 - 2
server/src/main/java/password/pwm/config/SettingUIFunction.java

@@ -20,7 +20,7 @@
 
 
 package password.pwm.config;
 package password.pwm.config;
 
 
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -29,7 +29,7 @@ public interface SettingUIFunction
 {
 {
     Serializable provideFunction(
     Serializable provideFunction(
             PwmRequest pwmRequest,
             PwmRequest pwmRequest,
-            StoredConfiguration storedConfiguration,
+            StoredConfigurationModifier modifier,
             PwmSetting setting,
             PwmSetting setting,
             String profile,
             String profile,
             String extraData
             String extraData

+ 1 - 2
server/src/main/java/password/pwm/config/StoredValue.java

@@ -31,7 +31,6 @@ import java.util.Locale;
 
 
 public interface StoredValue extends Serializable
 public interface StoredValue extends Serializable
 {
 {
-
     List<XmlElement> toXmlValues( String valueElementName, XmlOutputProcessData xmlOutputProcessData );
     List<XmlElement> toXmlValues( String valueElementName, XmlOutputProcessData xmlOutputProcessData );
 
 
     Object toNativeObject( );
     Object toNativeObject( );
@@ -52,5 +51,5 @@ public interface StoredValue extends Serializable
                 throws PwmException;
                 throws PwmException;
     }
     }
 
 
-    String valueHash( );
+    String valueHash();
 }
 }

+ 14 - 8
server/src/main/java/password/pwm/config/function/AbstractUriCertImportFunction.java

@@ -24,7 +24,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -45,7 +45,7 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction
     @Override
     @Override
     public String provideFunction(
     public String provideFunction(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier modifier,
             final PwmSetting setting,
             final PwmSetting setting,
             final String profile,
             final String profile,
             final String extraData )
             final String extraData )
@@ -54,17 +54,17 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final List<X509Certificate> certs;
         final List<X509Certificate> certs;
 
 
-        final String urlString = getUri( storedConfiguration, setting, profile, extraData );
+        final String urlString = getUri( modifier, setting, profile, extraData );
         try
         try
         {
         {
             final URI uri = URI.create( urlString );
             final URI uri = URI.create( urlString );
             if ( "https".equalsIgnoreCase( uri.getScheme() ) )
             if ( "https".equalsIgnoreCase( uri.getScheme() ) )
             {
             {
-                certs = X509Utils.readRemoteHttpCertificates( pwmRequest.getPwmApplication(), pwmSession.getLabel(), uri, new Configuration( storedConfiguration ) );
+                certs = X509Utils.readRemoteHttpCertificates( pwmRequest.getPwmApplication(), pwmSession.getLabel(), uri, new Configuration( modifier.newStoredConfiguration() ) );
             }
             }
             else
             else
             {
             {
-                final Configuration configuration = new Configuration( storedConfiguration );
+                final Configuration configuration = new Configuration( modifier.newStoredConfiguration() );
                 certs = X509Utils.readRemoteCertificates( URI.create( urlString ), configuration );
                 certs = X509Utils.readRemoteCertificates( URI.create( urlString ), configuration );
             }
             }
         }
         }
@@ -80,7 +80,7 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction
 
 
 
 
         final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
         final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
-        store( certs, storedConfiguration, setting, profile, extraData, userIdentity );
+        store( certs, modifier, setting, profile, extraData, userIdentity );
 
 
         final StringBuffer returnStr = new StringBuffer();
         final StringBuffer returnStr = new StringBuffer();
         for ( final X509Certificate loopCert : certs )
         for ( final X509Certificate loopCert : certs )
@@ -91,12 +91,18 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction
         return returnStr.toString();
         return returnStr.toString();
     }
     }
 
 
-    abstract String getUri( StoredConfiguration storedConfiguration, PwmSetting pwmSetting, String profile, String extraData ) throws PwmOperationalException;
+    abstract String getUri(
+            StoredConfigurationModifier modifier,
+            PwmSetting pwmSetting,
+            String profile,
+            String extraData
+    )
+            throws PwmOperationalException, PwmUnrecoverableException;
 
 
 
 
     void store(
     void store(
             final List<X509Certificate> certs,
             final List<X509Certificate> certs,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier storedConfiguration,
             final PwmSetting pwmSetting,
             final PwmSetting pwmSetting,
             final String profile,
             final String profile,
             final String extraData,
             final String extraData,

+ 11 - 5
server/src/main/java/password/pwm/config/function/ActionCertImportFunction.java

@@ -23,7 +23,7 @@ package password.pwm.config.function;
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.reflect.TypeToken;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
@@ -43,13 +43,19 @@ public class ActionCertImportFunction extends AbstractUriCertImportFunction
     private static final String KEY_WEB_ACTION_ITERATION = "webActionIter";
     private static final String KEY_WEB_ACTION_ITERATION = "webActionIter";
 
 
     @Override
     @Override
-    String getUri( final StoredConfiguration storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException
+    String getUri(
+            final StoredConfigurationModifier modifier,
+            final PwmSetting pwmSetting,
+            final String profile,
+            final String extraData
+    )
+            throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         final Map<String, Integer> extraDataMap = JsonUtil.deserialize( extraData, new TypeToken<Map<String, Integer>>()
         final Map<String, Integer> extraDataMap = JsonUtil.deserialize( extraData, new TypeToken<Map<String, Integer>>()
         {
         {
         } );
         } );
 
 
-        final ActionValue actionValue = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, profile );
+        final ActionValue actionValue = ( ActionValue ) modifier.newStoredConfiguration().readSetting( pwmSetting, profile );
         final ActionConfiguration action = ( actionValue.toNativeObject() ).get( extraDataMap.get( KEY_ITERATION ) );
         final ActionConfiguration action = ( actionValue.toNativeObject() ).get( extraDataMap.get( KEY_ITERATION ) );
         final ActionConfiguration.WebAction webAction = action.getWebActions().get( extraDataMap.get( KEY_WEB_ACTION_ITERATION ) );
         final ActionConfiguration.WebAction webAction = action.getWebActions().get( extraDataMap.get( KEY_WEB_ACTION_ITERATION ) );
 
 
@@ -79,7 +85,7 @@ public class ActionCertImportFunction extends AbstractUriCertImportFunction
 
 
     void store(
     void store(
             final List<X509Certificate> certs,
             final List<X509Certificate> certs,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier storedConfiguration,
             final PwmSetting pwmSetting,
             final PwmSetting pwmSetting,
             final String profile,
             final String profile,
             final String extraData,
             final String extraData,
@@ -91,7 +97,7 @@ public class ActionCertImportFunction extends AbstractUriCertImportFunction
         {
         {
         } );
         } );
 
 
-        final ActionValue actionValue = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, profile );
+        final ActionValue actionValue = ( ActionValue ) storedConfiguration.newStoredConfiguration().readSetting( pwmSetting, profile );
         final List<ActionConfiguration> actionConfigurations = actionValue.toNativeObject();
         final List<ActionConfiguration> actionConfigurations = actionValue.toNativeObject();
         final ActionConfiguration action = actionConfigurations.get( extraDataMap.get( KEY_ITERATION ) );
         final ActionConfiguration action = actionConfigurations.get( extraDataMap.get( KEY_ITERATION ) );
         final ActionConfiguration.WebAction webAction = action.getWebActions().get( extraDataMap.get( KEY_WEB_ACTION_ITERATION ) );
         final ActionConfiguration.WebAction webAction = action.getWebActions().get( extraDataMap.get( KEY_WEB_ACTION_ITERATION ) );

+ 4 - 4
server/src/main/java/password/pwm/config/function/LdapCertImportFunction.java

@@ -24,7 +24,7 @@ import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
@@ -43,7 +43,7 @@ public class LdapCertImportFunction implements SettingUIFunction
     @Override
     @Override
     public String provideFunction(
     public String provideFunction(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier modifier,
             final PwmSetting setting,
             final PwmSetting setting,
             final String profile,
             final String profile,
             final String extraData
             final String extraData
@@ -53,7 +53,7 @@ public class LdapCertImportFunction implements SettingUIFunction
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
 
 
-        final StringArrayValue ldapUrlsValue = ( StringArrayValue ) storedConfiguration.readSetting( PwmSetting.LDAP_SERVER_URLS, profile );
+        final StringArrayValue ldapUrlsValue = ( StringArrayValue ) modifier.newStoredConfiguration().readSetting( PwmSetting.LDAP_SERVER_URLS, profile );
         final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
         final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
         if ( ldapUrlsValue != null && ldapUrlsValue.toNativeObject() != null )
         if ( ldapUrlsValue != null && ldapUrlsValue.toNativeObject() != null )
         {
         {
@@ -62,7 +62,7 @@ public class LdapCertImportFunction implements SettingUIFunction
         }
         }
 
 
         final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
         final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
-        storedConfiguration.writeSetting( setting, profile, new X509CertificateValue( resultCertificates ), userIdentity );
+        modifier.writeSetting( setting, profile, new X509CertificateValue( resultCertificates ), userIdentity );
         return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig() );
         return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig() );
     }
     }
 
 

+ 6 - 4
server/src/main/java/password/pwm/config/function/OAuthCertImportFunction.java

@@ -22,10 +22,11 @@ package password.pwm.config.function;
 
 
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
 
 
 import java.net.URI;
 import java.net.URI;
@@ -35,7 +36,8 @@ public class OAuthCertImportFunction extends AbstractUriCertImportFunction
 
 
 
 
     @Override
     @Override
-    String getUri( final StoredConfiguration storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException
+    String getUri( final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final String extraData )
+            throws PwmOperationalException, PwmUnrecoverableException
     {
     {
 
 
         final String uriString;
         final String uriString;
@@ -44,12 +46,12 @@ public class OAuthCertImportFunction extends AbstractUriCertImportFunction
         switch ( pwmSetting )
         switch ( pwmSetting )
         {
         {
             case OAUTH_ID_CERTIFICATE:
             case OAUTH_ID_CERTIFICATE:
-                uriString = ( String ) storedConfiguration.readSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL ).toNativeObject();
+                uriString = ( String ) modifier.newStoredConfiguration().readSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL, null ).toNativeObject();
                 menuDebugLocation = PwmSetting.OAUTH_ID_CODERESOLVE_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
                 menuDebugLocation = PwmSetting.OAUTH_ID_CODERESOLVE_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
                 break;
                 break;
 
 
             case RECOVERY_OAUTH_ID_CERTIFICATE:
             case RECOVERY_OAUTH_ID_CERTIFICATE:
-                uriString = ( String ) storedConfiguration.readSetting( PwmSetting.RECOVERY_OAUTH_ID_CODERESOLVE_URL, profile ).toNativeObject();
+                uriString = ( String ) modifier.newStoredConfiguration().readSetting( PwmSetting.RECOVERY_OAUTH_ID_CODERESOLVE_URL, profile ).toNativeObject();
                 menuDebugLocation = PwmSetting.RECOVERY_OAUTH_ID_CERTIFICATE.toMenuLocationDebug( profile, PwmConstants.DEFAULT_LOCALE );
                 menuDebugLocation = PwmSetting.RECOVERY_OAUTH_ID_CERTIFICATE.toMenuLocationDebug( profile, PwmConstants.DEFAULT_LOCALE );
                 break;
                 break;
 
 

+ 7 - 6
server/src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java

@@ -22,7 +22,7 @@ package password.pwm.config.function;
 
 
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.RemoteWebServiceValue;
 import password.pwm.config.value.RemoteWebServiceValue;
 import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
@@ -39,9 +39,10 @@ public class RemoteWebServiceCertImportFunction extends AbstractUriCertImportFun
 {
 {
 
 
     @Override
     @Override
-    String getUri( final StoredConfiguration storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException
+    String getUri( final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final String extraData )
+            throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) storedConfiguration.readSetting( pwmSetting, profile );
+        final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) modifier.newStoredConfiguration().readSetting( pwmSetting, profile );
         final String serviceName = actionNameFromExtraData( extraData );
         final String serviceName = actionNameFromExtraData( extraData );
         final RemoteWebServiceConfiguration action = actionValue.forName( serviceName );
         final RemoteWebServiceConfiguration action = actionValue.forName( serviceName );
         final String uriString = action.getUrl();
         final String uriString = action.getUrl();
@@ -72,7 +73,7 @@ public class RemoteWebServiceCertImportFunction extends AbstractUriCertImportFun
 
 
     void store(
     void store(
             final List<X509Certificate> certs,
             final List<X509Certificate> certs,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier modifier,
             final PwmSetting pwmSetting,
             final PwmSetting pwmSetting,
             final String profile,
             final String profile,
             final String extraData,
             final String extraData,
@@ -80,7 +81,7 @@ public class RemoteWebServiceCertImportFunction extends AbstractUriCertImportFun
     )
     )
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) storedConfiguration.readSetting( pwmSetting, profile );
+        final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) modifier.newStoredConfiguration().readSetting( pwmSetting, profile );
         final String actionName = actionNameFromExtraData( extraData );
         final String actionName = actionNameFromExtraData( extraData );
         final List<RemoteWebServiceConfiguration> newList = new ArrayList<>();
         final List<RemoteWebServiceConfiguration> newList = new ArrayList<>();
         for ( final RemoteWebServiceConfiguration loopConfiguration : actionValue.toNativeObject() )
         for ( final RemoteWebServiceConfiguration loopConfiguration : actionValue.toNativeObject() )
@@ -98,7 +99,7 @@ public class RemoteWebServiceCertImportFunction extends AbstractUriCertImportFun
             }
             }
         }
         }
         final RemoteWebServiceValue newActionValue = new RemoteWebServiceValue( newList );
         final RemoteWebServiceValue newActionValue = new RemoteWebServiceValue( newList );
-        storedConfiguration.writeSetting( pwmSetting, profile, newActionValue, userIdentity );
+        modifier.writeSetting( pwmSetting, profile, newActionValue, userIdentity );
     }
     }
 
 
 }
 }

+ 5 - 3
server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java

@@ -22,22 +22,24 @@ package password.pwm.config.function;
 
 
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
 
 
 import java.net.URI;
 import java.net.URI;
 
 
 public class SMSGatewayCertImportFunction extends AbstractUriCertImportFunction
 public class SMSGatewayCertImportFunction extends AbstractUriCertImportFunction
 {
 {
     @Override
     @Override
-    String getUri( final StoredConfiguration storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException
+    String getUri( final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final String extraData )
+            throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         final String uriString;
         final String uriString;
         final String menuDebugLocation;
         final String menuDebugLocation;
 
 
-        uriString = ( String ) storedConfiguration.readSetting( PwmSetting.SMS_GATEWAY_URL ).toNativeObject();
+        uriString = ( String ) modifier.newStoredConfiguration().readSetting( PwmSetting.SMS_GATEWAY_URL, null ).toNativeObject();
         menuDebugLocation = PwmSetting.SMS_GATEWAY_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
         menuDebugLocation = PwmSetting.SMS_GATEWAY_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
 
 
         if ( uriString.isEmpty() )
         if ( uriString.isEmpty() )

+ 4 - 4
server/src/main/java/password/pwm/config/function/SmtpCertImportFunction.java

@@ -24,7 +24,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
@@ -41,7 +41,7 @@ public class SmtpCertImportFunction implements SettingUIFunction
     @Override
     @Override
     public String provideFunction(
     public String provideFunction(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier modifier,
             final PwmSetting setting,
             final PwmSetting setting,
             final String profile,
             final String profile,
             final String extraData
             final String extraData
@@ -50,12 +50,12 @@ public class SmtpCertImportFunction implements SettingUIFunction
     {
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
 
 
-        final Configuration configuration = new Configuration( storedConfiguration );
+        final Configuration configuration = new Configuration( modifier.newStoredConfiguration() );
         final List<X509Certificate> certs = EmailServerUtil.readCertificates( configuration, profile );
         final List<X509Certificate> certs = EmailServerUtil.readCertificates( configuration, profile );
         if ( !JavaHelper.isEmpty( certs ) )
         if ( !JavaHelper.isEmpty( certs ) )
         {
         {
             final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
             final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
-            storedConfiguration.writeSetting( PwmSetting.EMAIL_SERVER_CERTS, profile, new X509CertificateValue( certs ), userIdentity );
+            modifier.writeSetting( PwmSetting.EMAIL_SERVER_CERTS, profile, new X509CertificateValue( certs ), userIdentity );
         }
         }
 
 
         return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmRequest.getConfig() );
         return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmRequest.getConfig() );

+ 5 - 5
server/src/main/java/password/pwm/config/function/SyslogCertImportFunction.java

@@ -25,7 +25,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.SettingUIFunction;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -49,7 +49,7 @@ public class SyslogCertImportFunction implements SettingUIFunction
     @Override
     @Override
     public String provideFunction(
     public String provideFunction(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier modifier,
             final PwmSetting setting,
             final PwmSetting setting,
             final String profile,
             final String profile,
             final String extraData )
             final String extraData )
@@ -62,7 +62,7 @@ public class SyslogCertImportFunction implements SettingUIFunction
 
 
         final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
         final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
 
 
-        final List<String> syslogConfigStrs = ( List<String> ) storedConfiguration.readSetting( PwmSetting.AUDIT_SYSLOG_SERVERS ).toNativeObject();
+        final List<String> syslogConfigStrs = ( List<String> ) modifier.newStoredConfiguration().readSetting( PwmSetting.AUDIT_SYSLOG_SERVERS, null ).toNativeObject();
         if ( syslogConfigStrs != null && !syslogConfigStrs.isEmpty() )
         if ( syslogConfigStrs != null && !syslogConfigStrs.isEmpty() )
         {
         {
             for ( String entry : syslogConfigStrs )
             for ( String entry : syslogConfigStrs )
@@ -77,7 +77,7 @@ public class SyslogCertImportFunction implements SettingUIFunction
                             final List<X509Certificate> certs = X509Utils.readRemoteCertificates(
                             final List<X509Certificate> certs = X509Utils.readRemoteCertificates(
                                     syslogConfig.getHost(),
                                     syslogConfig.getHost(),
                                     syslogConfig.getPort(),
                                     syslogConfig.getPort(),
-                                    new Configuration( storedConfiguration )
+                                    new Configuration( modifier.newStoredConfiguration() )
                             );
                             );
                             if ( certs != null )
                             if ( certs != null )
                             {
                             {
@@ -98,7 +98,7 @@ public class SyslogCertImportFunction implements SettingUIFunction
         if ( !error )
         if ( !error )
         {
         {
             final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
             final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null;
-            storedConfiguration.writeSetting( setting, new X509CertificateValue( resultCertificates ), userIdentity );
+            modifier.writeSetting( setting, null, new X509CertificateValue( resultCertificates ), userIdentity );
             return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig() );
             return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig() );
         }
         }
         else
         else

+ 3 - 2
server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java

@@ -32,6 +32,7 @@ import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -58,7 +59,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
     @Override
     @Override
     public Serializable provideFunction(
     public Serializable provideFunction(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier storedConfiguration,
             final PwmSetting setting,
             final PwmSetting setting,
             final String profile,
             final String profile,
             final String extraData )
             final String extraData )
@@ -68,7 +69,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
 
 
         final Instant startSearchTime = Instant.now();
         final Instant startSearchTime = Instant.now();
         final int maxResultSize = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT ) );
         final int maxResultSize = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT ) );
-        final Collection<UserIdentity> users = discoverMatchingUsers( pwmApplication, maxResultSize, storedConfiguration, setting, profile );
+        final Collection<UserIdentity> users = discoverMatchingUsers( pwmApplication, maxResultSize, storedConfiguration.newStoredConfiguration(), setting, profile );
         final TimeDuration searchDuration = TimeDuration.fromCurrent( startSearchTime );
         final TimeDuration searchDuration = TimeDuration.fromCurrent( startSearchTime );
 
 
         final UserMatchViewerResults userMatchViewerResults = new UserMatchViewerResults();
         final UserMatchViewerResults userMatchViewerResults = new UserMatchViewerResults();

+ 0 - 90
server/src/main/java/password/pwm/config/stored/ComparingChangeLog.java

@@ -1,90 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2019 The PWM Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package password.pwm.config.stored;
-
-import password.pwm.config.StoredValue;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.java.TimeDuration;
-import password.pwm.util.logging.PwmLogger;
-
-import java.time.Instant;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class ComparingChangeLog implements ConfigChangeLog
-{
-    public static final PwmLogger LOGGER = PwmLogger.forClass( ComparingChangeLog.class );
-
-    private final StoredConfigurationSpi originalConfiguration;
-    private final StoredConfigurationSpi modifiedConfiguration;
-
-    private ComparingChangeLog(
-            final StoredConfiguration originalConfiguration,
-            final StoredConfiguration modifiedConfiguration
-    )
-    {
-        this.originalConfiguration = ( StoredConfigurationSpi ) originalConfiguration;
-        this.modifiedConfiguration = ( StoredConfigurationSpi ) modifiedConfiguration;
-    }
-
-    public static ComparingChangeLog create(
-            final StoredConfiguration originalConfiguration,
-            final StoredConfiguration modifiedConfiguration
-    )
-    {
-        return new ComparingChangeLog( originalConfiguration, modifiedConfiguration );
-    }
-
-    @Override
-    public Set<StoredConfigItemKey> changedValues ()
-    {
-        final Instant startTime = Instant.now();
-
-        final Set<StoredConfigItemKey> interestedReferences = new HashSet<>();
-        interestedReferences.addAll( StoredConfigurationUtil.modifiedItems( originalConfiguration ) );
-        interestedReferences.addAll( StoredConfigurationUtil.modifiedItems( modifiedConfiguration ) );
-
-        final Set<StoredConfigItemKey> deltaReferences = interestedReferences.parallelStream()
-                .filter( reference ->
-                        {
-                            final Optional<String> hash = originalConfiguration.readStoredValue( reference ).map( StoredValue::valueHash );
-                            final Optional<String> hash2 = modifiedConfiguration.readStoredValue( reference ).map( StoredValue::valueHash );
-                            return hash.isPresent() && hash2.isPresent() && !Objects.equals( hash.get(), hash2.get() );
-                        }
-                ).collect( Collectors.toSet() );
-
-        LOGGER.trace( () -> "generated changeLog items via compare in " + TimeDuration.compactFromCurrent( startTime ) );
-
-        return Collections.unmodifiableSet( deltaReferences );
-    }
-
-    @Override
-    public boolean isModified()
-            throws PwmUnrecoverableException
-    {
-        return !changedValues().isEmpty();
-    }
-
-}

+ 25 - 22
server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java

@@ -64,7 +64,7 @@ class ConfigurationCleaner
             new UpdatePropertiesWithoutType()
             new UpdatePropertiesWithoutType()
     ) );
     ) );
 
 
-    private static final List<PwmExceptionLoggingConsumer<StoredConfigurationSpi>> STORED_CONFIG_POST_PROCESSORS = Collections.unmodifiableList( Arrays.asList(
+    private static final List<PwmExceptionLoggingConsumer<StoredConfigurationModifier>> STORED_CONFIG_POST_PROCESSORS = Collections.unmodifiableList( Arrays.asList(
             new UpdateDeprecatedAdComplexitySettings(),
             new UpdateDeprecatedAdComplexitySettings(),
             new UpdateDeprecatedMinPwdLifetimeSetting(),
             new UpdateDeprecatedMinPwdLifetimeSetting(),
             new UpdateDeprecatedPublicHealthSetting()
             new UpdateDeprecatedPublicHealthSetting()
@@ -80,7 +80,7 @@ class ConfigurationCleaner
 
 
 
 
     static void postProcessStoredConfig(
     static void postProcessStoredConfig(
-            final StoredConfigurationSpi storedConfiguration
+            final StoredConfigurationModifier storedConfiguration
     )
     )
     {
     {
         STORED_CONFIG_POST_PROCESSORS.forEach( aClass -> PwmExceptionLoggingConsumer.wrapConsumer( aClass ).accept( storedConfiguration ) );
         STORED_CONFIG_POST_PROCESSORS.forEach( aClass -> PwmExceptionLoggingConsumer.wrapConsumer( aClass ).accept( storedConfiguration ) );
@@ -319,18 +319,19 @@ class ConfigurationCleaner
         }
         }
     }
     }
 
 
-    private static class UpdateDeprecatedAdComplexitySettings implements PwmExceptionLoggingConsumer<StoredConfigurationSpi>
+    private static class UpdateDeprecatedAdComplexitySettings implements PwmExceptionLoggingConsumer<StoredConfigurationModifier>
     {
     {
         @Override
         @Override
-        public void accept( final StoredConfigurationSpi storedConfiguration )
+        public void accept( final StoredConfigurationModifier modifier )
                 throws PwmUnrecoverableException
                 throws PwmUnrecoverableException
         {
         {
-            final Configuration configuration = new Configuration( storedConfiguration );
+            final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
+            final Configuration configuration = new Configuration( oldConfig );
             for ( final String profileID : configuration.getPasswordProfileIDs() )
             for ( final String profileID : configuration.getPasswordProfileIDs() )
             {
             {
-                if ( !storedConfiguration.isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) )
+                if ( !oldConfig.isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) )
                 {
                 {
-                    final boolean ad2003Enabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject();
+                    final boolean ad2003Enabled = ( boolean ) oldConfig.readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject();
                     final StoredValue value;
                     final StoredValue value;
                     if ( ad2003Enabled )
                     if ( ad2003Enabled )
                     {
                     {
@@ -342,30 +343,31 @@ class ConfigurationCleaner
                     }
                     }
                     LOGGER.info( () -> "converting deprecated non-default setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
                     LOGGER.info( () -> "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() );
                             + " to replacement setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + value.toNativeObject().toString() );
-                    final Optional<ValueMetaData> valueMetaData = storedConfiguration.readMetaData(
+                    final Optional<ValueMetaData> valueMetaData = oldConfig.readMetaData(
                             StoredConfigItemKey.fromSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) );
                             StoredConfigItemKey.fromSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) );
                     final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
                     final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
-                    storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, userIdentity );
+                    modifier.writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, userIdentity );
                 }
                 }
             }
             }
         }
         }
     }
     }
 
 
-    private static class UpdateDeprecatedMinPwdLifetimeSetting implements PwmExceptionLoggingConsumer<StoredConfigurationSpi>
+    private static class UpdateDeprecatedMinPwdLifetimeSetting implements PwmExceptionLoggingConsumer<StoredConfigurationModifier>
     {
     {
         @Override
         @Override
-        public void accept( final StoredConfigurationSpi storedConfiguration )
+        public void accept( final StoredConfigurationModifier modifier )
                 throws PwmUnrecoverableException
                 throws PwmUnrecoverableException
         {
         {
-            for ( final String profileID : storedConfiguration.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) )
+            final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
+            for ( final String profileID : oldConfig.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) )
             {
             {
-                if ( !storedConfiguration.isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) )
+                if ( !oldConfig.isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) )
                 {
                 {
-                    final boolean enforceEnabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject();
+                    final boolean enforceEnabled = ( boolean ) oldConfig.readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject();
                     final StoredValue value = enforceEnabled
                     final StoredValue value = enforceEnabled
                             ? new StringValue( RecoveryMinLifetimeOption.NONE.name() )
                             ? new StringValue( RecoveryMinLifetimeOption.NONE.name() )
                             : new StringValue( RecoveryMinLifetimeOption.ALLOW.name() );
                             : new StringValue( RecoveryMinLifetimeOption.ALLOW.name() );
-                    final ValueMetaData existingData = storedConfiguration.readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID );
+                    final ValueMetaData existingData = oldConfig.readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID );
                     final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null
                     final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null
                             ? existingData.getUserIdentity()
                             ? existingData.getUserIdentity()
                             : null;
                             : null;
@@ -373,33 +375,34 @@ class ConfigurationCleaner
                             + PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + "/" + profileID
                             + PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + "/" + profileID
                             + " to replacement setting " + PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE )
                             + " to replacement setting " + PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE )
                             + ", value=" + value.toNativeObject().toString() );
                             + ", value=" + value.toNativeObject().toString() );
-                    storedConfiguration.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor );
+                    modifier.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor );
                 }
                 }
             }
             }
         }
         }
     }
     }
 
 
-    private static class UpdateDeprecatedPublicHealthSetting implements PwmExceptionLoggingConsumer<StoredConfigurationSpi>
+    private static class UpdateDeprecatedPublicHealthSetting implements PwmExceptionLoggingConsumer<StoredConfigurationModifier>
     {
     {
         @Override
         @Override
-        public void accept( final StoredConfigurationSpi storedConfiguration )
+        public void accept( final StoredConfigurationModifier modifier )
                 throws PwmUnrecoverableException
                 throws PwmUnrecoverableException
         {
         {
-            if ( !storedConfiguration.isDefaultValue( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES ) )
+            final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
+            if ( !oldConfig.isDefaultValue( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null ) )
             {
             {
                 LOGGER.info( () -> "converting deprecated non-default setting "
                 LOGGER.info( () -> "converting deprecated non-default setting "
                         + PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE )
                         + PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE )
                         + " to replacement setting " + PwmSetting.WEBSERVICES_PUBLIC_ENABLE.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) );
                         + " to replacement setting " + PwmSetting.WEBSERVICES_PUBLIC_ENABLE.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) );
-                final Set<String> existingValues = ( Set<String> ) storedConfiguration.readSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE ).toNativeObject();
+                final Set<String> existingValues = ( Set<String> ) oldConfig.readSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null ).toNativeObject();
                 final Set<String> newValues = new LinkedHashSet<>( existingValues );
                 final Set<String> newValues = new LinkedHashSet<>( existingValues );
                 newValues.add( WebServiceUsage.Health.name() );
                 newValues.add( WebServiceUsage.Health.name() );
                 newValues.add( WebServiceUsage.Statistics.name() );
                 newValues.add( WebServiceUsage.Statistics.name() );
 
 
-                final Optional<ValueMetaData> valueMetaData = storedConfiguration.readMetaData(
+                final Optional<ValueMetaData> valueMetaData = oldConfig.readMetaData(
                         StoredConfigItemKey.fromSetting( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null ) );
                         StoredConfigItemKey.fromSetting( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null ) );
                 final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
                 final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
 
 
-                storedConfiguration.writeSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null, new OptionListValue( newValues ), userIdentity );
+                modifier.writeSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null, new OptionListValue( newValues ), userIdentity );
             }
             }
         }
         }
     }
     }

+ 0 - 1
server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java

@@ -28,7 +28,6 @@ public enum ConfigurationProperty
     NOTES( "notes" ),
     NOTES( "notes" ),
     PASSWORD_HASH( "configPasswordHash" ),
     PASSWORD_HASH( "configPasswordHash" ),
     STORE_PLAINTEXT_VALUES( "storePlaintextValues" ),
     STORE_PLAINTEXT_VALUES( "storePlaintextValues" ),
-    SAVE_CONFIG_ON_START( "saveConfigOnStart" ),
     MODIFICATION_TIMESTAMP( "modificationTimestamp" ),
     MODIFICATION_TIMESTAMP( "modificationTimestamp" ),
     IMPORT_LDAP_CERTIFICATES( "importLdapCertificates" ),;
     IMPORT_LDAP_CERTIFICATES( "importLdapCertificates" ),;
 
 

+ 7 - 4
server/src/main/java/password/pwm/config/stored/ConfigurationReader.java

@@ -85,7 +85,7 @@ public class ConfigurationReader
 
 
         if ( storedConfiguration == null )
         if ( storedConfiguration == null )
         {
         {
-            this.storedConfiguration = StoredConfigurationFactory.newStoredConfiguration();
+            this.storedConfiguration = StoredConfigurationFactory.newConfig();
         }
         }
 
 
         LOGGER.debug( () -> "configuration mode: " + configMode );
         LOGGER.debug( () -> "configuration mode: " + configMode );
@@ -106,7 +106,7 @@ public class ConfigurationReader
         if ( configuration == null )
         if ( configuration == null )
         {
         {
             final StoredConfiguration newStoredConfig = this.storedConfiguration == null
             final StoredConfiguration newStoredConfig = this.storedConfiguration == null
-                    ? StoredConfigurationFactory.newStoredConfiguration()
+                    ? StoredConfigurationFactory.newConfig()
                     : this.storedConfiguration;
                     : this.storedConfiguration;
             configuration = new Configuration( newStoredConfig );
             configuration = new Configuration( newStoredConfig );
         }
         }
@@ -248,7 +248,10 @@ public class ConfigurationReader
             {
             {
                 LOGGER.error( sessionLabel, "error trying to parse previous config epoch property: " + e.getMessage() );
                 LOGGER.error( sessionLabel, "error trying to parse previous config epoch property: " + e.getMessage() );
             }
             }
-            storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_EPOCH, newEpochStrValue );
+
+            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+            modifier.writeConfigProperty( ConfigurationProperty.CONFIG_EPOCH, newEpochStrValue );
+            this.storedConfiguration = modifier.newStoredConfiguration();
         }
         }
 
 
         if ( backupDirectory != null && !backupDirectory.exists() )
         if ( backupDirectory != null && !backupDirectory.exists() )
@@ -292,7 +295,7 @@ public class ConfigurationReader
         LOGGER.info( () -> "saved configuration in " + TimeDuration.compactFromCurrent( saveFileStartTime ) );
         LOGGER.info( () -> "saved configuration in " + TimeDuration.compactFromCurrent( saveFileStartTime ) );
         if ( pwmApplication != null )
         if ( pwmApplication != null )
         {
         {
-            final String actualChecksum = storedConfiguration.settingChecksum();
+            final String actualChecksum = storedConfiguration.valueHash();
             pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum );
             pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum );
         }
         }
 
 

+ 40 - 15
server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java

@@ -20,7 +20,6 @@
 
 
 package password.pwm.config.stored;
 package password.pwm.config.stored;
 
 
-import lombok.Value;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.Config;
@@ -33,7 +32,6 @@ import java.io.Serializable;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Objects;
 
 
-@Value
 public class StoredConfigItemKey implements Serializable, Comparable
 public class StoredConfigItemKey implements Serializable, Comparable
 {
 {
     public enum RecordType
     public enum RecordType
@@ -55,19 +53,33 @@ public class StoredConfigItemKey implements Serializable, Comparable
         }
         }
     }
     }
 
 
-
     private final RecordType recordType;
     private final RecordType recordType;
     private final String recordID;
     private final String recordID;
     private final String profileID;
     private final String profileID;
 
 
     private StoredConfigItemKey( final RecordType recordType, final String recordID, final String profileID )
     private StoredConfigItemKey( final RecordType recordType, final String recordID, final String profileID )
     {
     {
+        Objects.requireNonNull( recordType, "recordType can not be null" );
+        Objects.requireNonNull( recordID, "recordID can not be null" );
+
         this.recordType = recordType;
         this.recordType = recordType;
         this.recordID = recordID;
         this.recordID = recordID;
         this.profileID = profileID;
         this.profileID = profileID;
+    }
 
 
-        Objects.requireNonNull( recordType, "recordType can not be null" );
-        Objects.requireNonNull( recordID, "recordID can not be null" );
+    public RecordType getRecordType()
+    {
+        return recordType;
+    }
+
+    public String getRecordID()
+    {
+        return recordID;
+    }
+
+    public String getProfileID()
+    {
+        return profileID;
     }
     }
 
 
     static StoredConfigItemKey fromSetting( final PwmSetting pwmSetting, final String profileID )
     static StoredConfigItemKey fromSetting( final PwmSetting pwmSetting, final String profileID )
@@ -137,14 +149,7 @@ public class StoredConfigItemKey implements Serializable, Comparable
         }
         }
     }
     }
 
 
-
-    @Override
-    public String toString()
-    {
-        return "StoredConfigItemKey: " + toString( PwmConstants.DEFAULT_LOCALE  );
-    }
-
-    public String toString( final Locale locale )
+    public String getLabel( final Locale locale )
     {
     {
         final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
         final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
 
 
@@ -157,7 +162,6 @@ public class StoredConfigItemKey implements Serializable, Comparable
                 return recordType.getLabel() + separator + this.getRecordID();
                 return recordType.getLabel() + separator + this.getRecordID();
 
 
             case LOCALE_BUNDLE:
             case LOCALE_BUNDLE:
-                toLocaleBundle().getKey();
                 return recordType.getLabel()
                 return recordType.getLabel()
                         + separator
                         + separator
                         + this.getRecordID()
                         + this.getRecordID()
@@ -202,9 +206,30 @@ public class StoredConfigItemKey implements Serializable, Comparable
         return ConfigurationProperty.valueOf( recordID );
         return ConfigurationProperty.valueOf( recordID );
     }
     }
 
 
+    @Override
+    public int hashCode()
+    {
+        return toString().hashCode();
+    }
+
+    @Override
+    public boolean equals( final Object anotherObject )
+    {
+        return anotherObject != null
+                && anotherObject instanceof StoredConfigItemKey
+                && toString().equals( anotherObject.toString() );
+    }
+
+    @Override
+    public String toString()
+    {
+        return getLabel( PwmConstants.DEFAULT_LOCALE );
+       // return getRecordType().name() + "-" + this.getRecordID() + "-" + this.getProfileID();
+    }
+
     @Override
     @Override
     public int compareTo( final Object o )
     public int compareTo( final Object o )
     {
     {
-        return toString().compareTo( o.toString() );
+        return getLabel( PwmConstants.DEFAULT_LOCALE ).compareTo( o.toString() );
     }
     }
 }
 }

+ 6 - 33
server/src/main/java/password/pwm/config/stored/StoredConfiguration.java

@@ -20,9 +20,7 @@
 
 
 package password.pwm.config.stored;
 package password.pwm.config.stored;
 
 
-import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
@@ -43,12 +41,8 @@ public interface StoredConfiguration
 
 
     Instant modifyTime( );
     Instant modifyTime( );
 
 
-    StoredConfiguration copy() throws PwmUnrecoverableException;
-
     Optional<String> readConfigProperty( ConfigurationProperty propertyName );
     Optional<String> readConfigProperty( ConfigurationProperty propertyName );
 
 
-    void writeConfigProperty( ConfigurationProperty propertyName, String value );
-
     PwmSettingTemplateSet getTemplateSet();
     PwmSettingTemplateSet getTemplateSet();
 
 
     List<String> profilesForSetting( PwmSetting pwmSetting );
     List<String> profilesForSetting( PwmSetting pwmSetting );
@@ -57,38 +51,17 @@ public interface StoredConfiguration
 
 
     Map<String, String> readLocaleBundleMap( PwmLocaleBundle bundleName, String keyName );
     Map<String, String> readLocaleBundleMap( PwmLocaleBundle bundleName, String keyName );
 
 
-    void resetLocaleBundleMap( PwmLocaleBundle bundleName, String keyName );
-
-    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 );
     StoredValue readSetting( PwmSetting setting, String profileID );
 
 
-    void writeLocaleBundleMap( PwmLocaleBundle bundleName, String keyName, Map<String, String> localeMap );
-
-    void copyProfileID( PwmSettingCategory category, String sourceID, String destinationID, UserIdentity userIdentity )
-            throws PwmUnrecoverableException;
+    boolean isDefaultValue( PwmSetting setting, String profileID );
 
 
-    void writeSetting(
-            PwmSetting setting,
-            StoredValue value,
-            UserIdentity userIdentity
-    ) throws PwmUnrecoverableException;
+    String valueHash();
 
 
-    void writeSetting(
-            PwmSetting setting,
-            String profileID,
-            StoredValue value,
-            UserIdentity userIdentity
-    ) throws PwmUnrecoverableException;
+    Set<StoredConfigItemKey> modifiedItems();
 
 
+    Optional<ValueMetaData> readMetaData( StoredConfigItemKey storedConfigItemKey );
 
 
-    String settingChecksum() throws PwmUnrecoverableException;
+    Optional<StoredValue> readStoredValue( StoredConfigItemKey storedConfigItemKey );
 
 
-    Set<StoredConfigItemKey> modifiedSettings();
+    StoredConfiguration copy();
 }
 }

+ 40 - 24
server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java

@@ -71,18 +71,34 @@ public class StoredConfigurationFactory
     private static final String XML_FORMAT_VERSION = "5";
     private static final String XML_FORMAT_VERSION = "5";
 
 
 
 
-    public static StoredConfiguration newStoredConfiguration() throws PwmUnrecoverableException
+    public static StoredConfiguration newConfig() throws PwmUnrecoverableException
     {
     {
         final StoredConfiguration storedConfiguration = new StoredConfigurationImpl(  );
         final StoredConfiguration storedConfiguration = new StoredConfigurationImpl(  );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
 
 
-        StoredConfigurationUtil.initNewRandomSecurityKey( storedConfiguration );
-        storedConfiguration.writeConfigProperty(
+        StoredConfigurationUtil.initNewRandomSecurityKey( modifier );
+        modifier.writeConfigProperty(
                 ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) );
                 ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) );
-        storedConfiguration.writeConfigProperty(
+        modifier.writeConfigProperty(
                 ConfigurationProperty.CONFIG_EPOCH, String.valueOf( 0 ) );
                 ConfigurationProperty.CONFIG_EPOCH, String.valueOf( 0 ) );
 
 
 
 
-        return new StoredConfigurationImpl(  );
+        return modifier.newStoredConfiguration();
+    }
+
+    public static StoredConfigurationModifier newModifiableConfig() throws PwmUnrecoverableException
+    {
+        final StoredConfiguration storedConfiguration = new StoredConfigurationImpl(  );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+
+        StoredConfigurationUtil.initNewRandomSecurityKey( modifier );
+        modifier.writeConfigProperty(
+                ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) );
+        modifier.writeConfigProperty(
+                ConfigurationProperty.CONFIG_EPOCH, String.valueOf( 0 ) );
+
+
+        return modifier;
     }
     }
 
 
     public static StoredConfiguration fromXml( final InputStream inputStream )
     public static StoredConfiguration fromXml( final InputStream inputStream )
@@ -94,10 +110,10 @@ public class StoredConfigurationFactory
 
 
         final XmlInputDocumentReader xmlInputDocumentReader = new XmlInputDocumentReader( xmlDocument );
         final XmlInputDocumentReader xmlInputDocumentReader = new XmlInputDocumentReader( xmlDocument );
         final StoredConfigData storedConfigData = xmlInputDocumentReader.getStoredConfigData();
         final StoredConfigData storedConfigData = xmlInputDocumentReader.getStoredConfigData();
-        final StoredConfigurationSpi storedConfiguration = new StoredConfigurationImpl( storedConfigData );
-        ConfigurationCleaner.postProcessStoredConfig( storedConfiguration );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier(  new StoredConfigurationImpl( storedConfigData ) );
+        ConfigurationCleaner.postProcessStoredConfig( modifier );
 
 
-        return storedConfiguration;
+        return modifier.newStoredConfiguration();
     }
     }
 
 
     public static void toXml(
     public static void toXml(
@@ -212,7 +228,6 @@ public class StoredConfigurationFactory
                 final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
                 final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
                 final ValueMetaData metaData = readMetaDataFromXmlElement( key, settingElement.get() ).orElse( null );
                 final ValueMetaData metaData = readMetaDataFromXmlElement( key, settingElement.get() ).orElse( null );
                 return Optional.of( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) );
                 return Optional.of( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) );
-
             }
             }
             catch ( final PwmException e )
             catch ( final PwmException e )
             {
             {
@@ -328,32 +343,33 @@ public class StoredConfigurationFactory
         private Collection<StoredConfigData.ValueAndMetaCarrier> readLocaleBundles()
         private Collection<StoredConfigData.ValueAndMetaCarrier> readLocaleBundles()
         {
         {
             final List<StoredConfigData.ValueAndMetaCarrier> returnWrapper = new ArrayList<>();
             final List<StoredConfigData.ValueAndMetaCarrier> returnWrapper = new ArrayList<>();
-            for ( final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.values() )
+
+            for ( final XmlElement localeBundleElement : xpathForLocaleBundles() )
             {
             {
-                for ( final String key : pwmLocaleBundle.getDisplayKeys() )
+                final String bundleName = localeBundleElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_BUNDLE );
+                final Optional<PwmLocaleBundle> pwmLocaleBundle = PwmLocaleBundle.forKey( bundleName );
+                pwmLocaleBundle.ifPresent( ( bundle ) ->
                 {
                 {
-                    final String bundleName = pwmLocaleBundle.getKey();
-                    final Optional<XmlElement> localeBundleElement = xpathForLocaleBundleSetting( bundleName, key );
-
-                    if ( localeBundleElement.isPresent() )
+                    final String key = localeBundleElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY );
+                    if ( bundle.getDisplayKeys().contains( key ) )
                     {
                     {
                         final Map<String, String> bundleMap = new LinkedHashMap<>();
                         final Map<String, String> bundleMap = new LinkedHashMap<>();
-                        for ( final XmlElement valueElement : localeBundleElement.get().getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ) )
+                        for ( final XmlElement valueElement : localeBundleElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ) )
                         {
                         {
                             final String localeStrValue = valueElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE );
                             final String localeStrValue = valueElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE );
                             bundleMap.put( localeStrValue == null ? "" : localeStrValue, valueElement.getText() );
                             bundleMap.put( localeStrValue == null ? "" : localeStrValue, valueElement.getText() );
                         }
                         }
                         if ( !bundleMap.isEmpty() )
                         if ( !bundleMap.isEmpty() )
                         {
                         {
-                            final StoredConfigItemKey storedConfigItemKey = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, key );
+                            final StoredConfigItemKey storedConfigItemKey = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle.get(), key );
                             final StoredValue storedValue = new LocalizedStringValue( bundleMap );
                             final StoredValue storedValue = new LocalizedStringValue( bundleMap );
-                            final ValueMetaData metaData = readMetaDataFromXmlElement( storedConfigItemKey, localeBundleElement.get() ).orElse( null );
+                            final ValueMetaData metaData = readMetaDataFromXmlElement( storedConfigItemKey, localeBundleElement ).orElse( null );
                             returnWrapper.add( new StoredConfigData.ValueAndMetaCarrier( storedConfigItemKey, storedValue, metaData ) );
                             returnWrapper.add( new StoredConfigData.ValueAndMetaCarrier( storedConfigItemKey, storedValue, metaData ) );
                         }
                         }
                     }
                     }
-                }
+                } );
             }
             }
-            return returnWrapper;
+            return Collections.unmodifiableList( returnWrapper );
         }
         }
 
 
         private Optional<ValueMetaData> readMetaDataFromXmlElement( final StoredConfigItemKey key, final XmlElement xmlElement )
         private Optional<ValueMetaData> readMetaDataFromXmlElement( final StoredConfigItemKey key, final XmlElement xmlElement )
@@ -398,10 +414,10 @@ public class StoredConfigurationFactory
             return Optional.empty();
             return Optional.empty();
         }
         }
 
 
-        Optional<XmlElement> xpathForLocaleBundleSetting( final String bundleName, final String keyName )
+        List<XmlElement> xpathForLocaleBundles()
         {
         {
-            final String xpathString = "//localeBundle[@bundle=\"" + bundleName + "\"][@key=\"" + keyName + "\"]";
-            return document.evaluateXpathToElement( xpathString );
+            final String xpathString = "//localeBundle";
+            return document.evaluateXpathToElements( xpathString );
         }
         }
 
 
         XmlElement xpathForSettings()
         XmlElement xpathForSettings()
@@ -588,7 +604,7 @@ public class StoredConfigurationFactory
                 final XmlElement xmlElement
                 final XmlElement xmlElement
         )
         )
         {
         {
-            final Optional<ValueMetaData> valueMetaData = ( ( StoredConfigurationSpi ) storedConfiguration ).readMetaData( key );
+            final Optional<ValueMetaData> valueMetaData = ( ( StoredConfiguration ) storedConfiguration ).readMetaData( key );
 
 
             if ( valueMetaData.isPresent() )
             if ( valueMetaData.isPresent() )
             {
             {

+ 85 - 345
server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -20,85 +20,97 @@
 
 
 package password.pwm.config.stored;
 package password.pwm.config.stored;
 
 
-import password.pwm.PwmConstants;
-import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.config.value.LocalizedStringValue;
 import password.pwm.config.value.LocalizedStringValue;
-import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.StringValue;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.StringUtil;
-import password.pwm.util.java.TimeDuration;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.BCrypt;
+import password.pwm.util.secure.HmacAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.SecureEngine;
 import password.pwm.util.secure.SecureEngine;
 
 
 import java.time.Instant;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeMap;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.stream.Collectors;
+import java.util.function.Supplier;
 
 
 /**
 /**
+ * Immutable in-memory configuration.
+ *
  * @author Jason D. Rivard
  * @author Jason D. Rivard
  */
  */
-public class StoredConfigurationImpl implements StoredConfigurationSpi
+public class StoredConfigurationImpl implements StoredConfiguration
 {
 {
     private final String createTime;
     private final String createTime;
-    private Instant modifyTime;
+    private final Instant modifyTime;
+    private final Map<StoredConfigItemKey, StoredValue> storedValues;
+    private final Map<StoredConfigItemKey, ValueMetaData> metaValues;
+    private final PwmSettingTemplateSet templateSet;
 
 
-    private final Map<StoredConfigItemKey, StoredValue> storedValues = new TreeMap<>();
-    private final Map<StoredConfigItemKey, ValueMetaData> metaValues = new TreeMap<>();
+    private final transient Supplier<String> valueHashSupplier = new LazySupplier<>( this::valueHashImpl );
 
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationImpl.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationImpl.class );
 
 
-    private final ReentrantReadWriteLock modifyLock = new ReentrantReadWriteLock();
-
     StoredConfigurationImpl( final StoredConfigData storedConfigData )
     StoredConfigurationImpl( final StoredConfigData storedConfigData )
     {
     {
         this.createTime = storedConfigData.getCreateTime();
         this.createTime = storedConfigData.getCreateTime();
         this.modifyTime = storedConfigData.getModifyTime();
         this.modifyTime = storedConfigData.getModifyTime();
+        this.metaValues = Collections.unmodifiableMap(  new TreeMap<>( storedConfigData.getMetaDatas() ) );
+        this.templateSet = readTemplateSet( storedConfigData.getStoredValues() );
 
 
-        final Map<StoredConfigItemKey, StoredValue> filteredStoredValues = new HashMap<>( storedConfigData.getStoredValues() );
-        filteredStoredValues.keySet().retainAll( storedConfigData.getStoredValues().keySet().stream().filter( StoredConfigItemKey::isValid ).collect( Collectors.toList() ) );
-
-        final Map<StoredConfigItemKey, ValueMetaData> filteredMetaDatas = new HashMap<>( storedConfigData.getMetaDatas() );
-        filteredMetaDatas .keySet().retainAll( storedConfigData.getMetaDatas().keySet().stream().filter( StoredConfigItemKey::isValid ).collect( Collectors.toList() ) );
-
-        this.storedValues.putAll( filteredStoredValues );
-        this.metaValues.putAll( filteredMetaDatas );
+        final Map<StoredConfigItemKey, StoredValue> tempMap = new TreeMap<>( storedConfigData.getStoredValues() );
+        removeAllDefaultValues( tempMap, templateSet );
+        this.storedValues = Collections.unmodifiableMap( tempMap );
     }
     }
 
 
-    StoredConfigurationImpl( )
+    StoredConfigurationImpl()
     {
     {
         this.createTime = JavaHelper.toIsoDate( Instant.now() );
         this.createTime = JavaHelper.toIsoDate( Instant.now() );
         this.modifyTime = Instant.now();
         this.modifyTime = Instant.now();
+        this.storedValues = Collections.emptyMap();
+        this.metaValues = Collections.emptyMap();
+        this.templateSet = readTemplateSet( Collections.emptyMap() );
+    }
+
+    private static void removeAllDefaultValues( final Map<StoredConfigItemKey, StoredValue> valueMap, final PwmSettingTemplateSet pwmSettingTemplateSet )
+    {
+        valueMap.entrySet().removeIf( entry ->
+        {
+            final StoredConfigItemKey key = entry.getKey();
+            if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
+            {
+                final StoredValue loopValue = entry.getValue();
+                final StoredValue defaultValue = key.toPwmSetting().getDefaultValue( pwmSettingTemplateSet );
+                return Objects.equals( loopValue.valueHash(), defaultValue.valueHash() );
+            }
+            return false;
+        } );
     }
     }
 
 
+
+
     @Override
     @Override
     public StoredConfiguration copy()
     public StoredConfiguration copy()
     {
     {
-        return new StoredConfigurationImpl( new StoredConfigData( createTime, modifyTime, storedValues, metaValues ) );
+        return new StoredConfigurationImpl( asStoredConfigData() );
+    }
+
+    StoredConfigData asStoredConfigData()
+    {
+        return new StoredConfigData( createTime, modifyTime, storedValues, metaValues );
     }
     }
 
 
     @Override
     @Override
@@ -113,147 +125,58 @@ public class StoredConfigurationImpl implements StoredConfigurationSpi
         return Optional.empty();
         return Optional.empty();
     }
     }
 
 
-    @Override
-    public void writeConfigProperty(
-            final ConfigurationProperty propertyName,
-            final String value
-    )
+    public boolean isDefaultValue( final PwmSetting setting, final String profileID )
     {
     {
-        modifyLock.writeLock().lock();
-        try
-        {
-            final StoredConfigItemKey key = StoredConfigItemKey.fromConfigurationProperty( propertyName );
-
-            if ( StringUtil.isEmpty( value ) )
-            {
-                storedValues.remove( key );
-            }
-            else
-            {
-                final StoredValue storedValue = new StringValue( value );
-                storedValues.put( key, storedValue );
-            }
-        }
-        finally
-        {
-            modifyLock.writeLock().unlock();
-        }
+        final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
+        return !storedValues.containsKey( key );
     }
     }
 
 
-
     @Override
     @Override
     public Map<String, String> readLocaleBundleMap( final PwmLocaleBundle pwmLocaleBundle, final String keyName )
     public Map<String, String> readLocaleBundleMap( final PwmLocaleBundle pwmLocaleBundle, final String keyName )
     {
     {
-        modifyLock.readLock().lock();
-        try
+        final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName );
+        final StoredValue value = storedValues.get( key );
+        if ( value != null )
         {
         {
-            final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName );
-            final StoredValue value = storedValues.get( key );
-            if ( value != null )
-            {
-                return ( ( LocalizedStringValue ) value ).toNativeObject();
-            }
-        }
-        finally
-        {
-            modifyLock.readLock().unlock();
+            return ( ( LocalizedStringValue ) value ).toNativeObject();
         }
         }
         return Collections.emptyMap();
         return Collections.emptyMap();
     }
     }
 
 
     @Override
     @Override
-    public void resetLocaleBundleMap( final PwmLocaleBundle pwmLocaleBundle, final String keyName )
-    {
-        preModifyActions();
-        modifyLock.writeLock().lock();
-        try
-        {
-            final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName );
-            storedValues.remove( key );
-        }
-        finally
-        {
-            modifyLock.writeLock().unlock();
-        }
-    }
-
-    @Override
-    public void resetSetting( final PwmSetting setting, final String profileID, final UserIdentity userIdentity )
-    {
-        preModifyActions();
-        modifyLock.writeLock().lock();
-        try
-        {
-            final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
-            storedValues.remove( key );
-            metaValues.put( key, new ValueMetaData( Instant.now(), userIdentity ) );
-        }
-        finally
-        {
-            modifyLock.writeLock().unlock();
-        }
-    }
-
-    public boolean isDefaultValue( final PwmSetting setting )
-    {
-        return isDefaultValue( setting, null );
-    }
-
-    public boolean isDefaultValue( final PwmSetting setting, final String profileID )
-    {
-        modifyLock.readLock().lock();
-        try
-        {
-            final StoredValue currentValue = readSetting( setting, profileID );
-            final StoredValue defaultValue = defaultValue( setting, this.getTemplateSet() );
-            final String currentHashValue = currentValue.valueHash();
-            final String defaultHashValue = defaultValue.valueHash();
-            return Objects.equals( currentHashValue, defaultHashValue );
-        }
-        finally
-        {
-            modifyLock.readLock().unlock();
-        }
-    }
-
-    private static StoredValue defaultValue( final PwmSetting pwmSetting, final PwmSettingTemplateSet template )
+    public PwmSettingTemplateSet getTemplateSet()
     {
     {
-        return pwmSetting.getDefaultValue( template );
+        return templateSet;
     }
     }
 
 
-    @Override
-    public PwmSettingTemplateSet getTemplateSet()
+    private static PwmSettingTemplateSet readTemplateSet( final Map<StoredConfigItemKey, StoredValue> valueMap )
     {
     {
         final Set<PwmSettingTemplate> templates = new HashSet<>();
         final Set<PwmSettingTemplate> templates = new HashSet<>();
-        templates.add( readTemplateValue( PwmSetting.TEMPLATE_LDAP ) );
-        templates.add( readTemplateValue( PwmSetting.TEMPLATE_STORAGE ) );
-        templates.add( readTemplateValue( PwmSetting.DB_VENDOR_TEMPLATE ) );
+        readTemplateValue( valueMap, PwmSetting.TEMPLATE_LDAP ).ifPresent( templates::add );
+        readTemplateValue( valueMap, PwmSetting.TEMPLATE_STORAGE ).ifPresent( templates::add );
+        readTemplateValue( valueMap, PwmSetting.DB_VENDOR_TEMPLATE ).ifPresent( templates::add );
         return new PwmSettingTemplateSet( templates );
         return new PwmSettingTemplateSet( templates );
     }
     }
 
 
-    private PwmSettingTemplate readTemplateValue( final PwmSetting pwmSetting )
+    private static Optional<PwmSettingTemplate> readTemplateValue( final Map<StoredConfigItemKey, StoredValue> valueMap, final PwmSetting pwmSetting )
     {
     {
         final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( pwmSetting, null );
         final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( pwmSetting, null );
-        final StoredValue storedValue = storedValues.get( key );
+        final StoredValue storedValue = valueMap.get( key );
 
 
-        if ( storedValue  != null )
+        if ( storedValue != null )
         {
         {
             try
             try
             {
             {
                 final String strValue = ( String ) storedValue.toNativeObject();
                 final String strValue = ( String ) storedValue.toNativeObject();
-                return JavaHelper.readEnumFromString( PwmSettingTemplate.class, null, strValue );
+                return Optional.ofNullable( JavaHelper.readEnumFromString( PwmSettingTemplate.class, null, strValue ) );
             }
             }
-            catch ( IllegalStateException e )
+            catch ( final IllegalStateException e )
             {
             {
                 LOGGER.error( "error reading template", e );
                 LOGGER.error( "error reading template", e );
             }
             }
         }
         }
-        return null;
-    }
 
 
-    public void setTemplate( final PwmSettingTemplate template )
-    {
-        writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, template.toString() );
+        return Optional.empty();
     }
     }
 
 
     public String toString( final PwmSetting setting, final String profileID )
     public String toString( final PwmSetting setting, final String profileID )
@@ -262,251 +185,73 @@ public class StoredConfigurationImpl implements StoredConfigurationSpi
         return setting.getKey() + "=" + storedValue.toDebugString( null );
         return setting.getKey() + "=" + storedValue.toDebugString( null );
     }
     }
 
 
-
-    public Set<StoredConfigItemKey> modifiedSettings( )
+    public Set<StoredConfigItemKey> modifiedItems()
     {
     {
-        modifyLock.readLock().lock();
-        try
-        {
-            final Set<StoredConfigItemKey> modifiedKeys = new HashSet<>( storedValues.keySet() );
-            for ( final Iterator<StoredConfigItemKey> iterator = modifiedKeys.iterator(); iterator.hasNext(); )
-            {
-                final StoredConfigItemKey key = iterator.next();
-                if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
-                {
-                    if ( isDefaultValue( key.toPwmSetting(), key.getProfileID() ) )
-                    {
-                        iterator.remove();
-                    }
-                }
-            }
-            return Collections.unmodifiableSet( modifiedKeys );
-        }
-        finally
-        {
-            modifyLock.readLock().unlock();
-        }
+        return Collections.unmodifiableSet( storedValues.keySet() );
     }
     }
 
 
     @Override
     @Override
     public List<String> profilesForSetting( final PwmSetting pwmSetting )
     public List<String> profilesForSetting( final PwmSetting pwmSetting )
     {
     {
-        modifyLock.readLock().lock();
-        try
+        final List<String> returnObj = new ArrayList<>();
+        for ( final StoredConfigItemKey storedConfigItemKey : storedValues.keySet() )
         {
         {
-            final List<String> returnObj = new ArrayList<>(  );
-            for ( final StoredConfigItemKey storedConfigItemKey : storedValues.keySet() )
+            if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING
+                    && Objects.equals( storedConfigItemKey.getRecordID(), pwmSetting.getKey() ) )
             {
             {
-                if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING
-                        && Objects.equals( storedConfigItemKey.getRecordID(), pwmSetting.getKey() ) )
-                {
-                    returnObj.add( storedConfigItemKey.getProfileID() );
-                }
+                returnObj.add( storedConfigItemKey.getProfileID() );
             }
             }
-            return Collections.unmodifiableList( returnObj );
-        }
-        finally
-        {
-            modifyLock.readLock().unlock();
         }
         }
+        return Collections.unmodifiableList( returnObj );
     }
     }
 
 
-
     public ValueMetaData readSettingMetadata( final PwmSetting setting, final String profileID )
     public ValueMetaData readSettingMetadata( final PwmSetting setting, final String profileID )
     {
     {
         final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
         final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
         return metaValues.get( key );
         return metaValues.get( key );
     }
     }
 
 
-    public StoredValue readSetting( final PwmSetting setting )
-    {
-        return readSetting( setting, null );
-    }
-
     public StoredValue readSetting( final PwmSetting setting, final String profileID )
     public StoredValue readSetting( final PwmSetting setting, final String profileID )
     {
     {
-        modifyLock.readLock().lock();
-        try
-        {
-            final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
-            final StoredValue storedValue = storedValues.get( key );
-            if ( storedValue == null )
-            {
-                return defaultValue( setting, getTemplateSet() );
-            }
-
-            return storedValue;
-        }
-        finally
-        {
-            modifyLock.readLock().unlock();
-        }
-    }
-
-    @Override
-    public void writeLocaleBundleMap( final PwmLocaleBundle pwmLocaleBundle, final String keyName, final Map<String, String> localeMap )
-    {
-        preModifyActions();
-
-        try
-        {
-            modifyLock.writeLock().lock();
-            final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName );
-            final StoredValue value = new LocalizedStringValue( localeMap );
-            storedValues.put( key, value );
-        }
-        finally
-        {
-            modifyLock.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" );
-        }
-
+        final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
+        final StoredValue storedValue = storedValues.get( key );
+        if ( storedValue == null )
         {
         {
-            final Collection<PwmSettingCategory> interestedCategories = PwmSettingCategory.associatedProfileCategories( category );
-            for ( final PwmSettingCategory interestedCategory : interestedCategories )
-            {
-                for ( final PwmSetting pwmSetting : interestedCategory.getSettings() )
-                {
-                    if ( !isDefaultValue( pwmSetting, sourceID ) )
-                    {
-                        final StoredValue value = readSetting( pwmSetting, sourceID );
-                        writeSetting( pwmSetting, destinationID, value, userIdentity );
-                    }
-                }
-            }
+            return setting.getDefaultValue( getTemplateSet() );
         }
         }
 
 
-        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 );
+        return storedValue;
     }
     }
 
 
-    public void writeSetting(
-            final PwmSetting setting,
-            final String profileID,
-            final StoredValue value,
-            final UserIdentity userIdentity
-    )
+    public String valueHash()
     {
     {
-        if ( profileID == null && setting.getCategory().hasProfiles() )
-        {
-            throw new IllegalArgumentException( "writing 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();
-
-        modifyLock.writeLock().lock();
-        try
-        {
-            final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
-            if ( value == null )
-            {
-                storedValues.remove( key );
-            }
-            else
-            {
-                storedValues.put( key, value );
-            }
-            metaValues.put( key, new ValueMetaData( Instant.now(), userIdentity ) );
-        }
-        finally
-        {
-            modifyLock.writeLock().unlock();
-        }
+        return valueHashSupplier.get();
     }
     }
 
 
-    public String settingChecksum( )
-            throws PwmUnrecoverableException
+    private String valueHashImpl()
     {
     {
-        final Instant startTime = Instant.now();
-
-        final Set<StoredConfigItemKey> modifiedSettings = modifiedSettings();
+        final Set<StoredConfigItemKey> modifiedSettings = modifiedItems();
         final StringBuilder sb = new StringBuilder();
         final StringBuilder sb = new StringBuilder();
-        sb.append( "PwmSettingsChecksum" );
+
         for ( final StoredConfigItemKey storedConfigItemKey : modifiedSettings )
         for ( final StoredConfigItemKey storedConfigItemKey : modifiedSettings )
         {
         {
             final StoredValue storedValue = storedValues.get( storedConfigItemKey );
             final StoredValue storedValue = storedValues.get( storedConfigItemKey );
             sb.append( storedValue.valueHash() );
             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( )
-    {
-        modifyTime = Instant.now();
-    }
-
-    public void setPassword( final String password )
-            throws PwmOperationalException
-    {
-        if ( password == null || password.isEmpty() )
+        try
         {
         {
-            throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
-                    {
-                            "can not set blank password",
-                    }
-                    ) );
+            return SecureEngine.hmac( HmacAlgorithm.HMAC_SHA_512, getKey(), sb.toString() );
         }
         }
-        final String trimmedPassword = password.trim();
-        if ( trimmedPassword.length() < 1 )
+        catch ( final PwmUnrecoverableException e )
         {
         {
-            throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
-                    {
-                            "can not set blank password",
-                    }
-                    ) );
+            throw new IllegalStateException( e );
         }
         }
-
-
-        final String passwordHash = BCrypt.hashPassword( password );
-        this.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash );
     }
     }
 
 
     private PwmSecurityKey cachedKey;
     private PwmSecurityKey cachedKey;
 
 
-    public PwmSecurityKey getKey( ) throws PwmUnrecoverableException
+    public PwmSecurityKey getKey() throws PwmUnrecoverableException
     {
     {
         if ( cachedKey == null )
         if ( cachedKey == null )
         {
         {
@@ -515,11 +260,6 @@ public class StoredConfigurationImpl implements StoredConfigurationSpi
         return cachedKey;
         return cachedKey;
     }
     }
 
 
-    public boolean isModified( )
-    {
-        return true;
-    }
-
     @Override
     @Override
     public Instant modifyTime()
     public Instant modifyTime()
     {
     {

+ 266 - 0
server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java

@@ -20,6 +20,272 @@
 
 
 package password.pwm.config.stored;
 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.config.value.LocalizedStringValue;
+import password.pwm.config.value.StringArrayValue;
+import password.pwm.config.value.StringValue;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.secure.BCrypt;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
 public class StoredConfigurationModifier
 public class StoredConfigurationModifier
 {
 {
+    private final AtomicReference<StoredConfigData> ref = new AtomicReference<>( );
+
+    private StoredConfigurationModifier( final StoredConfiguration storedConfiguration )
+    {
+        this.ref.set( ( ( StoredConfigurationImpl ) storedConfiguration ).asStoredConfigData() );
+    }
+
+    public static StoredConfigurationModifier newModifier( final StoredConfiguration storedConfiguration )
+    {
+        return new StoredConfigurationModifier( storedConfiguration );
+    }
+
+    public StoredConfiguration newStoredConfiguration()
+    {
+        return new StoredConfigurationImpl( ref.get() );
+    }
+
+    public void writeSetting(
+            final PwmSetting setting,
+            final String profileID,
+            final StoredValue value,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        Objects.requireNonNull( setting );
+        Objects.requireNonNull( value );
+
+        update( ( storedConfigData ) ->
+        {
+            if ( StringUtil.isEmpty( profileID ) && setting.getCategory().hasProfiles() )
+            {
+                throw new IllegalArgumentException( "writing of setting " + setting.getKey() + " requires a non-null profileID" );
+            }
+            if ( !StringUtil.isEmpty( profileID ) && !setting.getCategory().hasProfiles() )
+            {
+                throw new IllegalArgumentException( "cannot specify profile for non-profile setting" );
+            }
+
+            final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
+
+
+            return storedConfigData.toBuilder()
+                    .storedValue( key, value )
+                    .metaData( key, new ValueMetaData( Instant.now(), userIdentity ) )
+                    .build();
+        } );
+    }
+
+    public void writeConfigProperty(
+            final ConfigurationProperty propertyName,
+            final String value
+    )
+            throws PwmUnrecoverableException
+    {
+        update( ( storedConfigData ) ->
+        {
+            final StoredConfigItemKey key = StoredConfigItemKey.fromConfigurationProperty( propertyName );
+            final Map<StoredConfigItemKey, StoredValue> existingStoredValues = new HashMap<>( storedConfigData.getStoredValues() );
+
+            if ( StringUtil.isEmpty( value ) )
+            {
+                existingStoredValues.remove( key );
+            }
+            else
+            {
+                final StoredValue storedValue = new StringValue( value );
+                existingStoredValues.put( key, storedValue );
+            }
+
+            return storedConfigData.toBuilder()
+                    .clearStoredValues()
+                    .storedValues( existingStoredValues )
+                    .build();
+        } );
+    }
+
+    public void resetLocaleBundleMap( final PwmLocaleBundle pwmLocaleBundle, final String keyName )
+            throws PwmUnrecoverableException
+    {
+        update( ( storedConfigData ) ->
+        {
+            final Map<StoredConfigItemKey, StoredValue> existingStoredValues = new HashMap<>( storedConfigData.getStoredValues() );
+
+            final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName );
+            existingStoredValues.remove( key );
+
+            return storedConfigData.toBuilder()
+                    .clearStoredValues()
+                    .storedValues( existingStoredValues )
+                    .build();
+        } );
+    }
+
+    public void resetSetting( final PwmSetting setting, final String profileID, final UserIdentity userIdentity )
+            throws PwmUnrecoverableException
+    {
+        update( ( storedConfigData ) ->
+        {
+            final Map<StoredConfigItemKey, StoredValue> existingStoredValues = new HashMap<>( storedConfigData.getStoredValues() );
+
+            final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
+            existingStoredValues.remove( key );
+
+            return storedConfigData.toBuilder()
+                    .clearStoredValues()
+                    .storedValues( existingStoredValues )
+                    .metaData( key, new ValueMetaData( Instant.now(), userIdentity ) )
+                    .build();
+        } );
+    }
+
+    public void writeLocaleBundleMap(
+            final PwmLocaleBundle pwmLocaleBundle,
+            final String keyName,
+            final Map<String, String> localeMap
+    )
+            throws PwmUnrecoverableException
+    {
+        update( ( storedConfigData ) ->
+        {
+            final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName );
+            final StoredValue value = new LocalizedStringValue( localeMap );
+
+            return storedConfigData.toBuilder()
+                    .storedValue( key, value )
+                    .build();
+        } );
+    }
+
+    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" );
+        }
+
+        update( ( storedConfigData ) ->
+        {
+            final StoredConfiguration oldStoredConfiguration = new StoredConfigurationImpl( storedConfigData );
+
+            final List<String> existingProfiles = oldStoredConfiguration.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" );
+            }
+
+            final Collection<PwmSettingCategory> interestedCategories = PwmSettingCategory.associatedProfileCategories( category );
+            for ( final PwmSettingCategory interestedCategory : interestedCategories )
+            {
+                for ( final PwmSetting pwmSetting : interestedCategory.getSettings() )
+                {
+                    if ( !oldStoredConfiguration.isDefaultValue( pwmSetting, sourceID ) )
+                    {
+                        final StoredValue value = oldStoredConfiguration.readSetting( pwmSetting, sourceID );
+                        writeSetting( pwmSetting, destinationID, value, userIdentity );
+                    }
+                }
+            }
+            final List<String> newProfileIDList = new ArrayList<>( existingProfiles );
+            newProfileIDList.add( destinationID );
+
+            final StoredValue value = new StringArrayValue( newProfileIDList );
+            final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( category.getProfileSetting(), null );
+            final ValueMetaData valueMetaData = new ValueMetaData( Instant.now(), userIdentity );
+
+            return storedConfigData.toBuilder()
+                    .storedValue( key, value )
+                    .metaData( key, valueMetaData )
+                    .build();
+
+        } );
+    }
+
+    public void setPassword( final String password )
+            throws PwmOperationalException, PwmUnrecoverableException
+    {
+        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 passwordHash = BCrypt.hashPassword( password );
+        this.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash );
+    }
+
+    private void update( final FunctionWithException<StoredConfigData> function ) throws PwmUnrecoverableException
+    {
+        try
+        {
+            ref.updateAndGet( storedConfigData ->
+            {
+                try
+                {
+                    return function.applyThrows( storedConfigData );
+                }
+                catch ( final PwmUnrecoverableException e )
+                {
+                    throw new RuntimeException( e );
+                }
+            } );
+        }
+        catch ( final RuntimeException e )
+        {
+            throw ( PwmUnrecoverableException ) e.getCause();
+        }
+        ref.updateAndGet( storedConfigData -> storedConfigData.toBuilder().modifyTime( Instant.now() ).build() );
+    }
+
+    interface FunctionWithException<T>
+    {
+        T applyThrows( T value ) throws PwmUnrecoverableException;
+    }
 }
 }

+ 79 - 111
server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java

@@ -27,14 +27,11 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
-import password.pwm.config.value.LocalizedStringValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PasswordValue;
-import password.pwm.config.value.StringValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
@@ -51,6 +48,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeMap;
@@ -115,7 +113,7 @@ public abstract class StoredConfigurationUtil
             profileSetting = pwmSetting.getCategory().getProfileSetting();
             profileSetting = pwmSetting.getCategory().getProfileSetting();
         }
         }
 
 
-        final Object nativeObject = storedConfiguration.readSetting( profileSetting ).toNativeObject();
+        final Object nativeObject = storedConfiguration.readSetting( profileSetting, null ).toNativeObject();
         final List<String> settingValues = ( List<String> ) nativeObject;
         final List<String> settingValues = ( List<String> ) nativeObject;
         final LinkedList<String> profiles = new LinkedList<>( settingValues );
         final LinkedList<String> profiles = new LinkedList<>( settingValues );
         profiles.removeIf( profile -> StringUtil.isEmpty( profile ) );
         profiles.removeIf( profile -> StringUtil.isEmpty( profile ) );
@@ -123,61 +121,15 @@ public abstract class StoredConfigurationUtil
 
 
     }
     }
 
 
-
-    public static List<StoredConfigItemKey> modifiedItems( final StoredConfiguration storedConfiguration )
-    {
-        final List<StoredConfigItemKey> returnObj = new ArrayList<>();
-
-        for ( final PwmSetting setting : PwmSetting.values() )
-        {
-            if ( setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles() )
-            {
-                if ( !storedConfiguration.isDefaultValue( setting, null ) )
-                {
-                    final StoredConfigItemKey storedConfigItemKey = StoredConfigItemKey.fromSetting( setting, null );
-                    returnObj.add( storedConfigItemKey );
-                }
-            }
-        }
-
-        for ( final PwmSettingCategory category : PwmSettingCategory.values() )
-        {
-            if ( category.hasProfiles() )
-            {
-                for ( final String profileID : profilesForSetting( category.getProfileSetting(), storedConfiguration ) )
-                {
-                    for ( final PwmSetting setting : category.getSettings() )
-                    {
-                        if ( !storedConfiguration.isDefaultValue( setting, profileID ) )
-                        {
-                            final StoredConfigItemKey storedConfigItemKey = StoredConfigItemKey.fromSetting( setting, profileID );
-                            returnObj.add( storedConfigItemKey );
-                        }
-                    }
-                }
-            }
-        }
-
-        return returnObj;
-    }
-
-    private static StoredValue configPropertyAsValue( final StoredConfiguration storedConfiguration, final ConfigurationProperty configurationProperty )
-    {
-        final Optional<String> value = storedConfiguration.readConfigProperty( configurationProperty );
-        return new StringValue( value.orElse( "" ) );
-    }
-
-    private static StoredValue displayKeyAsValue( final StoredConfiguration storedConfiguration, final PwmLocaleBundle bundleName, final String key )
-    {
-        final Map<String, String> displayBundle = storedConfiguration.readLocaleBundleMap( bundleName, key );
-        return new LocalizedStringValue( displayBundle );
-    }
-
-    public static String changeLogAsDebugString( final StoredConfiguration storedConfiguration, final ConfigChangeLog configChangeLog, final Locale locale )
+    public static String changeLogAsDebugString(
+            final StoredConfiguration storedConfiguration,
+            final Set<StoredConfigItemKey> configChangeLog,
+            final Locale locale
+    )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
 
 
-        final Map<String, String> outputMap = StoredConfigurationUtil.asDebugMap( storedConfiguration, configChangeLog.changedValues(), locale );
+        final Map<String, String> outputMap = StoredConfigurationUtil.makeDebugMap( storedConfiguration, configChangeLog, locale );
         final StringBuilder output = new StringBuilder();
         final StringBuilder output = new StringBuilder();
         if ( outputMap.isEmpty() )
         if ( outputMap.isEmpty() )
         {
         {
@@ -200,30 +152,34 @@ public abstract class StoredConfigurationUtil
 
 
     }
     }
 
 
-    public static void resetAllPasswordValuesWithComment( final StoredConfiguration storedConfiguration )
+    public static StoredConfiguration copyConfigAndBlankAllPasswords( final StoredConfiguration input )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedSettings() )
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( input );
+
+        for ( final StoredConfigItemKey storedConfigItemKey : input.modifiedItems() )
         {
         {
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             {
             {
                 final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
                 final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
                 if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
                 if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
                 {
                 {
-                    final ValueMetaData valueMetaData = storedConfiguration.readSettingMetadata( pwmSetting, storedConfigItemKey.getProfileID() );
+                    final ValueMetaData valueMetaData = input.readSettingMetadata( pwmSetting, storedConfigItemKey.getProfileID() );
                     final UserIdentity userIdentity = valueMetaData == null ? null : valueMetaData.getUserIdentity();
                     final UserIdentity userIdentity = valueMetaData == null ? null : valueMetaData.getUserIdentity();
                     final PasswordValue passwordValue = new PasswordValue( new PasswordData( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ) );
                     final PasswordValue passwordValue = new PasswordValue( new PasswordData( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ) );
-                    storedConfiguration.writeSetting( pwmSetting, storedConfigItemKey.getProfileID(), passwordValue, userIdentity );
+                    modifier.writeSetting( pwmSetting, storedConfigItemKey.getProfileID(), passwordValue, userIdentity );
                 }
                 }
             }
             }
         }
         }
 
 
 
 
-        final Optional<String> pwdHash = storedConfiguration.readConfigProperty( ConfigurationProperty.PASSWORD_HASH );
+        final Optional<String> pwdHash = input.readConfigProperty( ConfigurationProperty.PASSWORD_HASH );
         if ( pwdHash.isPresent() )
         if ( pwdHash.isPresent() )
         {
         {
-            storedConfiguration.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT );
+            modifier.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT );
         }
         }
+
+        return modifier.newStoredConfiguration();
     }
     }
 
 
     public static List<String> validateValues( final StoredConfiguration storedConfiguration )
     public static List<String> validateValues( final StoredConfiguration storedConfiguration )
@@ -231,7 +187,7 @@ public abstract class StoredConfigurationUtil
         final Instant startTime = Instant.now();
         final Instant startTime = Instant.now();
         final List<String> errorStrings = new ArrayList<>();
         final List<String> errorStrings = new ArrayList<>();
 
 
-        for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedSettings() )
+        for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedItems() )
         {
         {
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             {
             {
@@ -295,10 +251,10 @@ public abstract class StoredConfigurationUtil
                 return Collections.emptySet();
                 return Collections.emptySet();
             }
             }
 
 
-            return allSettingConfigRecordIDs()
+            return allPossibleSettingKeysForConfiguration( storedConfiguration )
                     .parallelStream()
                     .parallelStream()
                     .filter( s -> s.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
                     .filter( s -> s.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
-                    .filter( s -> matchSetting( s ) )
+                    .filter( this::matchSetting )
                     .collect( Collectors.toCollection( TreeSet::new ) );
                     .collect( Collectors.toCollection( TreeSet::new ) );
         }
         }
 
 
@@ -306,7 +262,6 @@ public abstract class StoredConfigurationUtil
                 final StoredConfigItemKey storedConfigItemKey
                 final StoredConfigItemKey storedConfigItemKey
         )
         )
         {
         {
-
             final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
             final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
             final StoredValue value = storedConfiguration.readSetting( pwmSetting, storedConfigItemKey.getProfileID() );
             final StoredValue value = storedConfiguration.readSetting( pwmSetting, storedConfigItemKey.getProfileID() );
 
 
@@ -389,40 +344,6 @@ public abstract class StoredConfigurationUtil
             }
             }
             return false;
             return false;
         }
         }
-
-        private Set<StoredConfigItemKey> allSettingConfigRecordIDs()
-        {
-            final Set<StoredConfigItemKey> loopResults = new HashSet<>( modifiedItems( storedConfiguration ) );
-            for ( final PwmSetting loopSetting : PwmSetting.values() )
-            {
-                if ( loopSetting.getCategory().hasProfiles() )
-                {
-                    for ( final String profile : storedConfiguration.profilesForSetting( loopSetting ) )
-                    {
-                        loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, profile ) );
-                    }
-                }
-                else
-                {
-                    loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, null ) );
-                }
-            }
-            return Collections.unmodifiableSet( loopResults );
-        }
-    }
-
-    static void verifySettingProfileValueIsAppropriate( final PwmSetting setting, final String profileID )
-    {
-        if ( profileID == null && setting.getCategory().hasProfiles() )
-        {
-            final 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" );
-        }
     }
     }
 
 
     public static boolean verifyPassword( final StoredConfiguration storedConfiguration, final String password )
     public static boolean verifyPassword( final StoredConfiguration storedConfiguration, final String password )
@@ -441,8 +362,8 @@ public abstract class StoredConfigurationUtil
         return passwordHash.isPresent();
         return passwordHash.isPresent();
     }
     }
 
 
-    public static void setPassword( final StoredConfiguration storedConfiguration, final String password )
-            throws PwmOperationalException
+    public static void setPassword( final StoredConfigurationModifier storedConfiguration, final String password )
+            throws PwmOperationalException, PwmUnrecoverableException
     {
     {
         if ( StringUtil.isEmpty( password ) )
         if ( StringUtil.isEmpty( password ) )
         {
         {
@@ -467,16 +388,16 @@ public abstract class StoredConfigurationUtil
         storedConfiguration.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash );
         storedConfiguration.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash );
     }
     }
 
 
-    public static void initNewRandomSecurityKey( final StoredConfiguration storedConfiguration )
+    public static void initNewRandomSecurityKey( final StoredConfigurationModifier modifier )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        if ( !storedConfiguration.isDefaultValue( PwmSetting.PWM_SECURITY_KEY ) )
+        if ( !modifier.newStoredConfiguration().isDefaultValue( PwmSetting.PWM_SECURITY_KEY, null ) )
         {
         {
             return;
             return;
         }
         }
 
 
-        storedConfiguration.writeSetting(
-                PwmSetting.PWM_SECURITY_KEY,
+        modifier.writeSetting(
+                PwmSetting.PWM_SECURITY_KEY, null,
                 new PasswordValue( new PasswordData( PwmRandom.getInstance().alphaNumericString( 1024 ) ) ),
                 new PasswordValue( new PasswordData( PwmRandom.getInstance().alphaNumericString( 1024 ) ) ),
                 null
                 null
         );
         );
@@ -485,20 +406,67 @@ public abstract class StoredConfigurationUtil
     }
     }
 
 
 
 
-    public static Map<String, String> asDebugMap(
+    public static Map<String, String> makeDebugMap(
             final StoredConfiguration storedConfiguration,
             final StoredConfiguration storedConfiguration,
             final Collection<StoredConfigItemKey> interestedItems,
             final Collection<StoredConfigItemKey> interestedItems,
             final Locale locale
             final Locale locale
     )
     )
     {
     {
-        final StoredConfigurationSpi storedConfigurationSpi = ( StoredConfigurationSpi ) storedConfiguration;
         final Map<String, String> outputMap = interestedItems.stream()
         final Map<String, String> outputMap = interestedItems.stream()
                 .filter( ( key ) -> key.getRecordType() != StoredConfigItemKey.RecordType.PROPERTY )
                 .filter( ( key ) -> key.getRecordType() != StoredConfigItemKey.RecordType.PROPERTY )
-                .filter( ( key ) -> storedConfigurationSpi.readStoredValue( key ).isPresent() )
+                .filter( ( key ) -> storedConfiguration.readStoredValue( key ).isPresent() )
                 .collect( Collectors.toMap(
                 .collect( Collectors.toMap(
-                        key -> key.toString( locale ),
-                        key -> storedConfigurationSpi.readStoredValue( key ).get().toDebugString( locale ) ) );
+                        key -> key.getLabel( locale ),
+                        key -> storedConfiguration.readStoredValue( key ).get().toDebugString( locale ) ) );
 
 
         return Collections.unmodifiableMap( new TreeMap<>( outputMap ) );
         return Collections.unmodifiableMap( new TreeMap<>( outputMap ) );
     }
     }
+
+    public static Set<StoredConfigItemKey> allPossibleSettingKeysForConfiguration(
+            final StoredConfiguration storedConfiguration
+    )
+    {
+        final Set<StoredConfigItemKey> loopResults = new HashSet<>();
+        for ( final PwmSetting loopSetting : PwmSetting.values() )
+        {
+            if ( loopSetting.getCategory().hasProfiles() )
+            {
+                for ( final String profile : storedConfiguration.profilesForSetting( loopSetting ) )
+                {
+                    loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, profile ) );
+                }
+            }
+            else
+            {
+                loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, null ) );
+            }
+        }
+        return Collections.unmodifiableSet( loopResults );
+    }
+
+    public static Set<StoredConfigItemKey> changedValues (
+            final StoredConfiguration originalConfiguration,
+            final StoredConfiguration modifiedConfiguration
+    )
+    {
+        final Instant startTime = Instant.now();
+
+        final Set<StoredConfigItemKey> interestedReferences = new HashSet<>();
+        interestedReferences.addAll( originalConfiguration.modifiedItems() );
+        interestedReferences.addAll( modifiedConfiguration.modifiedItems() );
+
+        final Set<StoredConfigItemKey> deltaReferences = interestedReferences
+                .parallelStream()
+                .filter( reference ->
+                        {
+                            final Optional<String> hash1 = originalConfiguration.readStoredValue( reference ).map( StoredValue::valueHash );
+                            final Optional<String> hash2 = modifiedConfiguration.readStoredValue( reference ).map( StoredValue::valueHash );
+                            return hash1.isPresent() && hash2.isPresent() && !Objects.equals( hash1.get(), hash2.get() );
+                        }
+                ).collect( Collectors.toSet() );
+
+        LOGGER.trace( () -> "generated changeLog items via compare in " + TimeDuration.compactFromCurrent( startTime ) );
+
+        return Collections.unmodifiableSet( deltaReferences );
+    }
 }
 }

+ 28 - 6
server/src/main/java/password/pwm/config/value/AbstractValue.java

@@ -22,17 +22,27 @@ package password.pwm.config.value;
 
 
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
+import password.pwm.config.stored.StoredConfigXmlConstants;
+import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.LazySupplier;
+import password.pwm.util.java.XmlDocument;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
+import password.pwm.util.secure.PwmHashAlgorithm;
+import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.SecureEngine;
 import password.pwm.util.secure.SecureEngine;
 
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.Serializable;
 import java.io.Serializable;
+import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 
 
 public abstract class AbstractValue implements StoredValue
 public abstract class AbstractValue implements StoredValue
 {
 {
-    private transient LazySupplier<String> valueHash = new LazySupplier<>( this::valueHashImpl );
+    private final transient LazySupplier<String> valueHashSupplier = new LazySupplier<>( () -> valueHashComputer( AbstractValue.this ) );
 
 
     public String toString()
     public String toString()
     {
     {
@@ -58,18 +68,30 @@ public abstract class AbstractValue implements StoredValue
     }
     }
 
 
     @Override
     @Override
-    public String valueHash()
+    public final String valueHash()
     {
     {
-        return valueHash.get();
+        return valueHashSupplier.get();
     }
     }
 
 
-    protected String valueHashImpl()
+    static String valueHashComputer( final StoredValue storedValue )
     {
     {
         try
         try
         {
         {
-            return SecureEngine.hash( JsonUtil.serialize( ( Serializable ) this.toNativeObject() ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD );
+            final PwmSecurityKey testingKey = new PwmSecurityKey( "test" );
+            final XmlOutputProcessData xmlOutputProcessData = XmlOutputProcessData.builder()
+                    .pwmSecurityKey( testingKey )
+                    .storedValueEncoderMode( StoredValueEncoder.Mode.PLAIN )
+                    .build();
+            final List<XmlElement> xmlValues = storedValue.toXmlValues( StoredConfigXmlConstants.XML_ELEMENT_VALUE, xmlOutputProcessData );
+            final XmlDocument document = XmlFactory.getFactory().newDocument( "root" );
+            document.getRootElement().addContent( xmlValues );
+            final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            XmlFactory.getFactory().outputDocument( document, byteArrayOutputStream );
+            final String stringToHash = new String( byteArrayOutputStream.toByteArray(), PwmConstants.DEFAULT_CHARSET );
+            return SecureEngine.hash( stringToHash, PwmHashAlgorithm.SHA512 );
+
         }
         }
-        catch ( final PwmUnrecoverableException e )
+        catch ( final IOException | PwmUnrecoverableException e )
         {
         {
             throw new IllegalStateException( e );
             throw new IllegalStateException( e );
         }
         }

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

@@ -112,7 +112,7 @@ public class BooleanValue implements StoredValue
     }
     }
 
 
     @Override
     @Override
-    public String valueHash( )
+    public String valueHash()
     {
     {
         return value ? "1" : "0";
         return value ? "1" : "0";
     }
     }

+ 0 - 13
server/src/main/java/password/pwm/config/value/FileValue.java

@@ -22,7 +22,6 @@ package password.pwm.config.value;
 
 
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Value;
 import lombok.Value;
-import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.StoredConfigXmlConstants;
@@ -258,18 +257,6 @@ public class FileValue extends AbstractValue implements StoredValue
         return Collections.unmodifiableList( returnObj );
         return Collections.unmodifiableList( returnObj );
     }
     }
 
 
-    protected String valueHashImpl()
-    {
-        try
-        {
-            return SecureEngine.hash( JsonUtil.serializeCollection( toInfoMap() ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            throw new IllegalStateException( e );
-        }
-    }
-
     @Value
     @Value
     @Builder
     @Builder
     public static class FileInfo implements Serializable
     public static class FileInfo implements Serializable

+ 6 - 9
server/src/main/java/password/pwm/config/value/NamedSecretValue.java

@@ -33,6 +33,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlFactory;
 import password.pwm.util.java.XmlFactory;
@@ -51,6 +52,9 @@ import java.util.Optional;
 
 
 public class NamedSecretValue implements StoredValue
 public class NamedSecretValue implements StoredValue
 {
 {
+
+    private final transient LazySupplier<String> valueHashSupplier = new LazySupplier<>( () -> AbstractValue.valueHashComputer( NamedSecretValue.this ) );
+
     private static final String ELEMENT_NAME = "name";
     private static final String ELEMENT_NAME = "name";
     private static final String ELEMENT_PASSWORD = "password";
     private static final String ELEMENT_PASSWORD = "password";
     private static final String ELEMENT_USAGE = "usage";
     private static final String ELEMENT_USAGE = "usage";
@@ -248,15 +252,8 @@ public class NamedSecretValue implements StoredValue
     }
     }
 
 
     @Override
     @Override
-    public String valueHash( )
+    public String valueHash()
     {
     {
-        try
-        {
-            return values == null ? "" : SecureEngine.hash( JsonUtil.serializeMap( values ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            throw new IllegalStateException( e );
-        }
+        return valueHashSupplier.get();
     }
     }
 }
 }

+ 5 - 10
server/src/main/java/password/pwm/config/value/PasswordValue.java

@@ -32,10 +32,10 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlFactory;
 import password.pwm.util.java.XmlFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
-import password.pwm.util.secure.SecureEngine;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.Collections;
 import java.util.Collections;
@@ -45,6 +45,8 @@ import java.util.Optional;
 
 
 public class PasswordValue implements StoredValue
 public class PasswordValue implements StoredValue
 {
 {
+    private final transient LazySupplier<String> valueHashSupplier = new LazySupplier<>( () -> AbstractValue.valueHashComputer( PasswordValue.this ) );
+
     private final PasswordData value;
     private final PasswordData value;
 
 
     PasswordValue( )
     PasswordValue( )
@@ -194,15 +196,8 @@ public class PasswordValue implements StoredValue
     }
     }
 
 
     @Override
     @Override
-    public String valueHash( )
+    public String valueHash()
     {
     {
-        try
-        {
-            return value == null ? "" : SecureEngine.hash( JsonUtil.serialize( value.getStringValue() ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            throw new IllegalStateException( e );
-        }
+        return valueHashSupplier.get();
     }
     }
 }
 }

+ 2 - 2
server/src/main/java/password/pwm/health/CertificateChecker.java

@@ -29,7 +29,6 @@ import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
-import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
@@ -46,6 +45,7 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
+import java.util.Set;
 
 
 public class CertificateChecker implements HealthChecker
 public class CertificateChecker implements HealthChecker
 {
 {
@@ -95,7 +95,7 @@ public class CertificateChecker implements HealthChecker
         final StoredConfiguration storedConfiguration = configuration.getStoredConfiguration();
         final StoredConfiguration storedConfiguration = configuration.getStoredConfiguration();
 
 
         final List<HealthRecord> returnList = new ArrayList<>();
         final List<HealthRecord> returnList = new ArrayList<>();
-        final List<StoredConfigItemKey> modifiedReferences = StoredConfigurationUtil.modifiedItems( storedConfiguration );
+        final Set<StoredConfigItemKey> modifiedReferences = storedConfiguration.modifiedItems();
         for ( final StoredConfigItemKey storedConfigItemKey : modifiedReferences )
         for ( final StoredConfigItemKey storedConfigItemKey : modifiedReferences )
         {
         {
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
             if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )

+ 14 - 19
server/src/main/java/password/pwm/health/ConfigurationChecker.java

@@ -230,7 +230,7 @@ public class ConfigurationChecker implements HealthChecker
 
 
             try
             try
             {
             {
-                for ( final StoredConfigItemKey key : config.getStoredConfiguration().modifiedSettings() )
+                for ( final StoredConfigItemKey key : config.getStoredConfiguration().modifiedItems() )
                 {
                 {
                     final Instant startTime = Instant.now();
                     final Instant startTime = Instant.now();
                     if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
                     if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
@@ -393,30 +393,25 @@ public class ConfigurationChecker implements HealthChecker
                 {
                 {
                     if ( loopSetting.getCategory().hasProfiles() )
                     if ( loopSetting.getCategory().hasProfiles() )
                     {
                     {
-                        try
+
+                        final List<String> profiles = config.getStoredConfiguration().profilesForSetting( loopSetting );
+                        for ( final String profile : profiles )
                         {
                         {
-                            final List<String> profiles = config.getStoredConfiguration().profilesForSetting( loopSetting );
-                            for ( final String profile : profiles )
+                            final StoredValue storedValue = config.getStoredConfiguration().readSetting( loopSetting, profile );
+                            final List<FormConfiguration> forms = (List<FormConfiguration>) storedValue.toNativeObject();
+                            for ( final FormConfiguration form : forms )
                             {
                             {
-                                final StoredValue storedValue = config.getStoredConfiguration().readSetting( loopSetting, profile );
-                                final List<FormConfiguration> forms = (List<FormConfiguration>) storedValue.toNativeObject();
-                                for ( final FormConfiguration form : forms )
+                                if ( !StringUtil.isEmpty( form.getJavascript() ) )
                                 {
                                 {
-                                    if ( !StringUtil.isEmpty( form.getJavascript() ) )
-                                    {
-                                        records.add( HealthRecord.forMessage(
-                                                HealthMessage.Config_DeprecatedJSForm,
-                                                loopSetting.toMenuLocationDebug( profile, locale ),
-                                                PwmSetting.DISPLAY_CUSTOM_JAVASCRIPT.toMenuLocationDebug( null, locale )
-                                        ) );
-                                    }
+                                    records.add( HealthRecord.forMessage(
+                                            HealthMessage.Config_DeprecatedJSForm,
+                                            loopSetting.toMenuLocationDebug( profile, locale ),
+                                            PwmSetting.DISPLAY_CUSTOM_JAVASCRIPT.toMenuLocationDebug( null, locale )
+                                    ) );
                                 }
                                 }
                             }
                             }
                         }
                         }
-                        catch ( PwmUnrecoverableException e )
-                        {
-                            LOGGER.error( "unexpected error examining profiles for deprecated form  js option check: " + e.getMessage() );
-                        }
+
                     }
                     }
                     else
                     else
                     {
                     {

+ 43 - 48
server/src/main/java/password/pwm/http/ContextManager.java

@@ -33,6 +33,7 @@ import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -42,7 +43,9 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PropertyConfigurationImporter;
 import password.pwm.util.PropertyConfigurationImporter;
 import password.pwm.util.PwmScheduler;
 import password.pwm.util.PwmScheduler;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogManager;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.X509Utils;
 import password.pwm.util.secure.X509Utils;
 
 
@@ -59,6 +62,7 @@ import java.nio.file.Path;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Date;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
@@ -253,7 +257,13 @@ public class ContextManager implements Serializable
         }
         }
 
 
         final Collection<PwmEnvironment.ApplicationFlag> applicationFlags = parameterReader.readApplicationFlags();
         final Collection<PwmEnvironment.ApplicationFlag> applicationFlags = parameterReader.readApplicationFlags();
-        final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = parameterReader.readApplicationParams();
+        final Map<PwmEnvironment.ApplicationParameter, String> applicationParams = parameterReader.readApplicationParams( applicationPath );
+
+        if ( applicationParams != null && applicationParams.containsKey( PwmEnvironment.ApplicationParameter.InitConsoleLogLevel ) )
+        {
+            final String logLevel = applicationParams.get( PwmEnvironment.ApplicationParameter.InitConsoleLogLevel );
+            PwmLogManager.preInitConsoleLogLevel( logLevel );
+        }
 
 
         try
         try
         {
         {
@@ -295,8 +305,6 @@ public class ContextManager implements Serializable
                 taskMaster.scheduleWithFixedDelay( new ConfigFileWatcher(), fileScanFrequencyMs, fileScanFrequencyMs, TimeUnit.MILLISECONDS );
                 taskMaster.scheduleWithFixedDelay( new ConfigFileWatcher(), fileScanFrequencyMs, fileScanFrequencyMs, TimeUnit.MILLISECONDS );
             }
             }
 
 
-            checkConfigForSaveOnRestart( configReader, pwmApplication );
-
             checkConfigForAutoImportLdapCerts( configReader );
             checkConfigForAutoImportLdapCerts( configReader );
         }
         }
 
 
@@ -308,41 +316,6 @@ public class ContextManager implements Serializable
         LOGGER.trace( SESSION_LABEL, () -> "initialization complete (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
         LOGGER.trace( SESSION_LABEL, () -> "initialization complete (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
     }
     }
 
 
-    private void checkConfigForSaveOnRestart(
-            final ConfigurationReader configReader,
-            final PwmApplication pwmApplication
-    )
-    {
-        if ( configReader == null || configReader.getStoredConfiguration() == null )
-        {
-            return;
-        }
-
-        {
-            final Optional<String> configOnStart = configReader.getStoredConfiguration().readConfigProperty( ConfigurationProperty.SAVE_CONFIG_ON_START );
-            if ( !configOnStart.isPresent() || !Boolean.parseBoolean( configOnStart.get() ) )
-            {
-                return;
-            }
-        }
-
-        LOGGER.warn( SESSION_LABEL, "configuration file contains property \""
-                + ConfigurationProperty.SAVE_CONFIG_ON_START.getKey() + "\"=true, will save configuration and set property to false." );
-
-        try
-        {
-            final StoredConfiguration newConfig = configReader.getStoredConfiguration().copy();
-            newConfig.writeConfigProperty( ConfigurationProperty.SAVE_CONFIG_ON_START, "false" );
-            configReader.saveConfiguration( newConfig, pwmApplication, SESSION_LABEL );
-            requestPwmApplicationRestart();
-        }
-        catch ( Exception e )
-        {
-            LOGGER.error( SESSION_LABEL, "error while saving configuration file commanded by property \""
-                    + ConfigurationProperty.SAVE_CONFIG_ON_START + "\"=true, error: " + e.getMessage() );
-        }
-    }
-
     private void checkConfigForAutoImportLdapCerts(
     private void checkConfigForAutoImportLdapCerts(
             final ConfigurationReader configReader
             final ConfigurationReader configReader
     )
     )
@@ -681,17 +654,38 @@ public class ContextManager implements Serializable
             return PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem( contextPath );
             return PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem( contextPath );
         }
         }
 
 
-        Map<PwmEnvironment.ApplicationParameter, String> readApplicationParams( )
+        Map<PwmEnvironment.ApplicationParameter, String> readApplicationParams( final File applicationPath  )
         {
         {
-            final String contextAppParamsValue = readEnvironmentParameter( PwmEnvironment.EnvironmentParameter.applicationParamFile );
+            // attempt to read app params finle from specified env param file value
+            {
+                final String contextAppParamsValue = readEnvironmentParameter( PwmEnvironment.EnvironmentParameter.applicationParamFile );
+                if ( !StringUtil.isEmpty( contextAppParamsValue ) )
+                {
+                    return PwmEnvironment.ParseHelper.readAppParametersFromPath( contextAppParamsValue );
+                }
+            }
 
 
-            if ( contextAppParamsValue != null && !contextAppParamsValue.isEmpty() )
+            // attempt to read app params file from specified system file value
             {
             {
-                return PwmEnvironment.ParseHelper.parseApplicationParamValueParameter( contextAppParamsValue );
+                final String contextPath = servletContext.getContextPath().replace( "/", "" );
+                final Map<PwmEnvironment.ApplicationParameter, String> results = PwmEnvironment.ParseHelper.readApplicationParmsFromSystem( contextPath );
+                if ( !results.isEmpty() )
+                {
+                    return results;
+                }
             }
             }
 
 
-            final String contextPath = servletContext.getContextPath().replace( "/", "" );
-            return PwmEnvironment.ParseHelper.readApplicationParmsFromSystem( contextPath );
+            // attempt to read via application.properties in applicationPath
+            if ( applicationPath != null && applicationPath.exists() )
+            {
+                final File appPropertiesFile = new File( applicationPath.getPath() + File.separator + "application.properties" );
+                if ( appPropertiesFile.exists() )
+                {
+                    return PwmEnvironment.ParseHelper.readAppParametersFromPath( appPropertiesFile.getPath() );
+                }
+            }
+
+            return Collections.emptyMap();
         }
         }
 
 
 
 
@@ -743,7 +737,7 @@ public class ContextManager implements Serializable
             LOGGER.trace( SESSION_LABEL, () -> "beginning auto-import ldap cert due to config property '"
             LOGGER.trace( SESSION_LABEL, () -> "beginning auto-import ldap cert due to config property '"
                     + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() + "'" );
                     + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() + "'" );
             final Configuration configuration = new Configuration( configReader.getStoredConfiguration() );
             final Configuration configuration = new Configuration( configReader.getStoredConfiguration() );
-            final StoredConfiguration newStoredConfig = configReader.getStoredConfiguration() .copy();
+            final StoredConfigurationModifier modifiedConfig = StoredConfigurationModifier.newModifier( configReader.getStoredConfiguration() );
 
 
             int importedCerts = 0;
             int importedCerts = 0;
             for ( final LdapProfile ldapProfile : configuration.getLdapProfiles().values() )
             for ( final LdapProfile ldapProfile : configuration.getLdapProfiles().values() )
@@ -760,7 +754,8 @@ public class ContextManager implements Serializable
                             LOGGER.trace( SESSION_LABEL, () -> "imported cert: " + X509Utils.makeDebugText( cert ) );
                             LOGGER.trace( SESSION_LABEL, () -> "imported cert: " + X509Utils.makeDebugText( cert ) );
                         }
                         }
                         final StoredValue storedValue = new X509CertificateValue( certs );
                         final StoredValue storedValue = new X509CertificateValue( certs );
-                        newStoredConfig.writeSetting( PwmSetting.LDAP_SERVER_CERTS, ldapProfile.getIdentifier(), storedValue, null );
+
+                        modifiedConfig.writeSetting( PwmSetting.LDAP_SERVER_CERTS, ldapProfile.getIdentifier(), storedValue, null );
                     }
                     }
 
 
                 }
                 }
@@ -772,8 +767,8 @@ public class ContextManager implements Serializable
                 LOGGER.trace( SESSION_LABEL, () -> "completed auto-import ldap cert due to config property '"
                 LOGGER.trace( SESSION_LABEL, () -> "completed auto-import ldap cert due to config property '"
                         + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() + "'"
                         + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() + "'"
                         + ", imported " + totalImportedCerts + " certificates" );
                         + ", imported " + totalImportedCerts + " certificates" );
-                newStoredConfig.writeConfigProperty( ConfigurationProperty.IMPORT_LDAP_CERTIFICATES, "false" );
-                configReader.saveConfiguration( newStoredConfig, pwmApplication, SESSION_LABEL );
+                modifiedConfig.writeConfigProperty( ConfigurationProperty.IMPORT_LDAP_CERTIFICATES, "false" );
+                configReader.saveConfiguration( modifiedConfig.newStoredConfiguration(), pwmApplication, SESSION_LABEL );
                 requestPwmApplicationRestart();
                 requestPwmApplicationRestart();
             }
             }
             else
             else

+ 2 - 42
server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java

@@ -20,18 +20,19 @@
 
 
 package password.pwm.http.bean;
 package password.pwm.http.bean;
 
 
+import lombok.Data;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 
 
 import java.util.Collections;
 import java.util.Collections;
 import java.util.Set;
 import java.util.Set;
 
 
+@Data
 public class ConfigManagerBean extends PwmSessionBean
 public class ConfigManagerBean extends PwmSessionBean
 {
 {
     private transient StoredConfiguration storedConfiguration;
     private transient StoredConfiguration storedConfiguration;
     private boolean passwordVerified;
     private boolean passwordVerified;
     private boolean configUnlockedWarningShown;
     private boolean configUnlockedWarningShown;
-
     private String prePasswordEntryUrl;
     private String prePasswordEntryUrl;
 
 
     public ConfigManagerBean( )
     public ConfigManagerBean( )
@@ -43,47 +44,6 @@ public class ConfigManagerBean extends PwmSessionBean
         return Type.AUTHENTICATED;
         return Type.AUTHENTICATED;
     }
     }
 
 
-
-    public StoredConfiguration getStoredConfiguration( )
-    {
-        return storedConfiguration;
-    }
-
-    public void setConfiguration( final StoredConfiguration storedConfiguration )
-    {
-        this.storedConfiguration = storedConfiguration;
-    }
-
-    public boolean isPasswordVerified( )
-    {
-        return passwordVerified;
-    }
-
-    public void setPasswordVerified( final boolean passwordVerified )
-    {
-        this.passwordVerified = passwordVerified;
-    }
-
-    public String getPrePasswordEntryUrl( )
-    {
-        return prePasswordEntryUrl;
-    }
-
-    public void setPrePasswordEntryUrl( final String prePasswordEntryUrl )
-    {
-        this.prePasswordEntryUrl = prePasswordEntryUrl;
-    }
-
-    public boolean isConfigUnlockedWarningShown( )
-    {
-        return configUnlockedWarningShown;
-    }
-
-    public void setConfigUnlockedWarningShown( final boolean configUnlockedWarningShown )
-    {
-        this.configUnlockedWarningShown = configUnlockedWarningShown;
-    }
-
     @Override
     @Override
     public Set<SessionBeanMode> supportedModes( )
     public Set<SessionBeanMode> supportedModes( )
     {
     {

+ 49 - 36
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -40,6 +40,7 @@ import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.ValueMetaData;
 import password.pwm.config.stored.ValueMetaData;
 import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.ActionValue;
@@ -178,7 +179,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         if ( configManagerBean.getStoredConfiguration() == null )
         if ( configManagerBean.getStoredConfiguration() == null )
         {
         {
             final StoredConfiguration loadedConfig = ConfigManagerServlet.readCurrentConfiguration( pwmRequest );
             final StoredConfiguration loadedConfig = ConfigManagerServlet.readCurrentConfiguration( pwmRequest );
-            configManagerBean.setConfiguration( loadedConfig );
+            configManagerBean.setStoredConfiguration( loadedConfig );
         }
         }
 
 
         return ProcessStatus.Continue;
         return ProcessStatus.Continue;
@@ -212,8 +213,10 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         try
         try
         {
         {
             final Class implementingClass = Class.forName( functionName );
             final Class implementingClass = Class.forName( functionName );
-            final SettingUIFunction function = ( SettingUIFunction ) implementingClass.newInstance();
-            final Serializable result = function.provideFunction( pwmRequest, configManagerBean.getStoredConfiguration(), pwmSetting, profileID, extraData );
+            final SettingUIFunction function = ( SettingUIFunction ) implementingClass.getDeclaredConstructor().newInstance();
+            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
+            final Serializable result = function.provideFunction( pwmRequest, modifier, pwmSetting, profileID, extraData );
+            configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
             final RestResultBean restResultBean = RestResultBean.forSuccessMessage( result, pwmRequest, Message.Success_Unknown );
             final RestResultBean restResultBean = RestResultBean.forSuccessMessage( result, pwmRequest, Message.Success_Unknown );
             pwmRequest.outputJsonResult( restResultBean );
             pwmRequest.outputJsonResult( restResultBean );
         }
         }
@@ -253,8 +256,9 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         {
         {
             final StringTokenizer st = new StringTokenizer( key, "-" );
             final StringTokenizer st = new StringTokenizer( key, "-" );
             st.nextToken();
             st.nextToken();
-            final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( st.nextToken() )
-                    .orElseThrow( () -> new IllegalArgumentException( "unknown locale bundle name" ) );
+            final String localeBundleName = st.nextToken();
+            final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( localeBundleName )
+                    .orElseThrow( () -> new IllegalArgumentException( "unknown locale bundle name '" + localeBundleName + "'" ) );
             final String keyName = st.nextToken();
             final String keyName = st.nextToken();
             final Map<String, String> bundleMap = storedConfig.readLocaleBundleMap( pwmLocaleBundle, keyName );
             final Map<String, String> bundleMap = storedConfig.readLocaleBundleMap( pwmLocaleBundle, keyName );
             if ( bundleMap == null || bundleMap.isEmpty() )
             if ( bundleMap == null || bundleMap.isEmpty() )
@@ -364,7 +368,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
             throws IOException, PwmUnrecoverableException
     {
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final StoredConfiguration storedConfig = configManagerBean.getStoredConfiguration();
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
         final String key = pwmRequest.readParameterAsString( "key" );
         final String key = pwmRequest.readParameterAsString( "key" );
         final String bodyString = pwmRequest.readRequestBodyAsString();
         final String bodyString = pwmRequest.readRequestBodyAsString();
         final PwmSetting setting = PwmSetting.forKey( key );
         final PwmSetting setting = PwmSetting.forKey( key );
@@ -383,7 +387,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             final Map<String, String> valueMap = JsonUtil.deserializeStringMap( bodyString );
             final Map<String, String> valueMap = JsonUtil.deserializeStringMap( bodyString );
             final Map<String, String> outputMap = new LinkedHashMap<>( valueMap );
             final Map<String, String> outputMap = new LinkedHashMap<>( valueMap );
 
 
-            storedConfig.writeLocaleBundleMap( pwmLocaleBundle, keyName, outputMap );
+            modifier.writeLocaleBundleMap( pwmLocaleBundle, keyName, outputMap );
             returnMap.put( "isDefault", outputMap.isEmpty() );
             returnMap.put( "isDefault", outputMap.isEmpty() );
             returnMap.put( "key", key );
             returnMap.put( "key", key );
         }
         }
@@ -398,9 +402,9 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                 {
                 {
                     returnMap.put( "errorMessage", setting.getLabel( pwmRequest.getLocale() ) + ": " + errorMsgs.get( 0 ) );
                     returnMap.put( "errorMessage", setting.getLabel( pwmRequest.getLocale() ) + ": " + errorMsgs.get( 0 ) );
                 }
                 }
-                storedConfig.writeSetting( setting, profileID, storedValue, loggedInUser );
+                modifier.writeSetting( setting, profileID, storedValue, loggedInUser );
             }
             }
-            catch ( Exception e )
+            catch ( final Exception e )
             {
             {
                 final String errorMsg = "error writing default value for setting " + setting.toString() + ", error: " + e.getMessage();
                 final String errorMsg = "error writing default value for setting " + setting.toString() + ", error: " + e.getMessage();
                 LOGGER.error( errorMsg, e );
                 LOGGER.error( errorMsg, e );
@@ -409,8 +413,9 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             returnMap.put( "key", key );
             returnMap.put( "key", key );
             returnMap.put( "category", setting.getCategory().toString() );
             returnMap.put( "category", setting.getCategory().toString() );
             returnMap.put( "syntax", setting.getSyntax().toString() );
             returnMap.put( "syntax", setting.getSyntax().toString() );
-            returnMap.put( "isDefault", storedConfig.isDefaultValue( setting, profileID ) );
+            returnMap.put( "isDefault", configManagerBean.getStoredConfiguration().isDefaultValue( setting, profileID ) );
         }
         }
+        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         pwmRequest.outputJsonResult( RestResultBean.withData( returnMap ) );
         pwmRequest.outputJsonResult( RestResultBean.withData( returnMap ) );
         return ProcessStatus.Halt;
         return ProcessStatus.Halt;
     }
     }
@@ -422,7 +427,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
             throws IOException, PwmUnrecoverableException
     {
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final StoredConfiguration storedConfig = configManagerBean.getStoredConfiguration();
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
         final UserIdentity loggedInUser = pwmRequest.getUserInfoIfLoggedIn();
         final UserIdentity loggedInUser = pwmRequest.getUserInfoIfLoggedIn();
         final String key = pwmRequest.readParameterAsString( "key" );
         final String key = pwmRequest.readParameterAsString( "key" );
         final PwmSetting setting = PwmSetting.forKey( key );
         final PwmSetting setting = PwmSetting.forKey( key );
@@ -434,14 +439,15 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( st.nextToken() )
             final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( st.nextToken() )
                     .orElseThrow( () -> new IllegalArgumentException( "unknown locale bundle name" ) );
                     .orElseThrow( () -> new IllegalArgumentException( "unknown locale bundle name" ) );
             final String keyName = st.nextToken();
             final String keyName = st.nextToken();
-            storedConfig.resetLocaleBundleMap( pwmLocaleBundle, keyName );
+            modifier.resetLocaleBundleMap( pwmLocaleBundle, keyName );
         }
         }
         else
         else
         {
         {
             final String profileID = setting.getCategory().hasProfiles() ? pwmRequest.readParameterAsString( "profile" ) : null;
             final String profileID = setting.getCategory().hasProfiles() ? pwmRequest.readParameterAsString( "profile" ) : null;
-            storedConfig.resetSetting( setting, profileID, loggedInUser );
+            modifier.resetSetting( setting, profileID, loggedInUser );
         }
         }
 
 
+        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         return ProcessStatus.Halt;
         return ProcessStatus.Halt;
     }
     }
@@ -453,24 +459,26 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, ServletException, PwmUnrecoverableException
             throws IOException, ServletException, PwmUnrecoverableException
     {
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
 
 
         try
         try
         {
         {
             final Map<String, String> postData = pwmRequest.readBodyAsJsonStringMap();
             final Map<String, String> postData = pwmRequest.readBodyAsJsonStringMap();
             final String password = postData.get( "password" );
             final String password = postData.get( "password" );
-            StoredConfigurationUtil.setPassword( configManagerBean.getStoredConfiguration(), password );
+            StoredConfigurationUtil.setPassword( modifier, password );
             configManagerBean.setPasswordVerified( true );
             configManagerBean.setPasswordVerified( true );
             LOGGER.debug( pwmRequest, () -> "config password updated" );
             LOGGER.debug( pwmRequest, () -> "config password updated" );
             final RestResultBean restResultBean = RestResultBean.forConfirmMessage( pwmRequest, Config.Confirm_ConfigPasswordStored );
             final RestResultBean restResultBean = RestResultBean.forConfirmMessage( pwmRequest, Config.Confirm_ConfigPasswordStored );
 
 
             pwmRequest.outputJsonResult( restResultBean );
             pwmRequest.outputJsonResult( restResultBean );
         }
         }
-        catch ( PwmOperationalException e )
+        catch ( final PwmOperationalException e )
         {
         {
             final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest );
             final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest );
             pwmRequest.outputJsonResult( restResultBean );
             pwmRequest.outputJsonResult( restResultBean );
         }
         }
 
 
+        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         return ProcessStatus.Halt;
         return ProcessStatus.Halt;
     }
     }
 
 
@@ -498,12 +506,12 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             try
             try
             {
             {
                 ConfigManagerServlet.saveConfiguration( pwmRequest, configManagerBean.getStoredConfiguration() );
                 ConfigManagerServlet.saveConfiguration( pwmRequest, configManagerBean.getStoredConfiguration() );
-                configManagerBean.setConfiguration( null );
-                configManagerBean.setConfiguration( null );
+                configManagerBean.setStoredConfiguration( null );
+                configManagerBean.setStoredConfiguration( null );
                 LOGGER.debug( pwmSession, () -> "save configuration operation completed" );
                 LOGGER.debug( pwmSession, () -> "save configuration operation completed" );
                 pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
                 pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
             }
             }
-            catch ( PwmUnrecoverableException e )
+            catch ( final PwmUnrecoverableException e )
             {
             {
                 final ErrorInformation errorInfo = e.getErrorInformation();
                 final ErrorInformation errorInfo = e.getErrorInformation();
                 pwmRequest.outputJsonResult( RestResultBean.fromError( errorInfo, pwmRequest ) );
                 pwmRequest.outputJsonResult( RestResultBean.fromError( errorInfo, pwmRequest ) );
@@ -521,7 +529,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, ServletException, PwmUnrecoverableException
             throws IOException, ServletException, PwmUnrecoverableException
     {
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        configManagerBean.setConfiguration( null );
+        configManagerBean.setStoredConfiguration( null );
         pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         return ProcessStatus.Halt;
         return ProcessStatus.Halt;
     }
     }
@@ -534,19 +542,19 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
             throws IOException, PwmUnrecoverableException
     {
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
+        final StoredConfigurationModifier modifer = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
         {
         {
             final String updateDescriptionTextCmd = pwmRequest.readParameterAsString( "updateNotesText" );
             final String updateDescriptionTextCmd = pwmRequest.readParameterAsString( "updateNotesText" );
-            if ( updateDescriptionTextCmd != null && "true".equalsIgnoreCase( updateDescriptionTextCmd ) )
+            if ( StringUtil.nullSafeEqualsIgnoreCase( "true", updateDescriptionTextCmd ) )
             {
             {
                 try
                 try
                 {
                 {
                     final String bodyString = pwmRequest.readRequestBodyAsString();
                     final String bodyString = pwmRequest.readRequestBodyAsString();
                     final String value = JsonUtil.deserialize( bodyString, String.class );
                     final String value = JsonUtil.deserialize( bodyString, String.class );
-                    configManagerBean.getStoredConfiguration().writeConfigProperty( ConfigurationProperty.NOTES,
-                            value );
+                    modifer.writeConfigProperty( ConfigurationProperty.NOTES, value );
                     LOGGER.trace( () -> "updated notesText" );
                     LOGGER.trace( () -> "updated notesText" );
                 }
                 }
-                catch ( Exception e )
+                catch ( final Exception e )
                 {
                 {
                     LOGGER.error( "error updating notesText: " + e.getMessage() );
                     LOGGER.error( "error updating notesText: " + e.getMessage() );
                 }
                 }
@@ -558,20 +566,19 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                     try
                     try
                     {
                     {
                         final PwmSettingTemplate template = PwmSettingTemplate.valueOf( requestedTemplate );
                         final PwmSettingTemplate template = PwmSettingTemplate.valueOf( requestedTemplate );
-                        configManagerBean.getStoredConfiguration().writeConfigProperty(
-                                ConfigurationProperty.LDAP_TEMPLATE, template.toString() );
+                        modifer.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, template.toString() );
                         LOGGER.trace( () -> "setting template to: " + requestedTemplate );
                         LOGGER.trace( () -> "setting template to: " + requestedTemplate );
                     }
                     }
-                    catch ( IllegalArgumentException e )
+                    catch ( final IllegalArgumentException e )
                     {
                     {
-                        configManagerBean.getStoredConfiguration().writeConfigProperty(
-                                ConfigurationProperty.LDAP_TEMPLATE, PwmSettingTemplate.DEFAULT.toString() );
+                        modifer.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, PwmSettingTemplate.DEFAULT.toString() );
                         LOGGER.error( "unknown template set request: " + requestedTemplate );
                         LOGGER.error( "unknown template set request: " + requestedTemplate );
                     }
                     }
                 }
                 }
             }
             }
         }
         }
 
 
+        configManagerBean.setStoredConfiguration( modifer.newStoredConfiguration() );
         return ProcessStatus.Halt;
         return ProcessStatus.Halt;
     }
     }
 
 
@@ -745,11 +752,13 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException, IOException, ServletException
             throws PwmUnrecoverableException, IOException, ServletException
     {
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
+
+
         final String key = pwmRequest.readParameterAsString( "key" );
         final String key = pwmRequest.readParameterAsString( "key" );
         final PwmSetting setting = PwmSetting.forKey( key );
         final PwmSetting setting = PwmSetting.forKey( key );
         final int maxFileSize = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.CONFIG_MAX_JDBC_JAR_SIZE ) );
         final int maxFileSize = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.CONFIG_MAX_JDBC_JAR_SIZE ) );
 
 
-
         if ( setting == PwmSetting.HTTPS_CERT )
         if ( setting == PwmSetting.HTTPS_CERT )
         {
         {
             try
             try
@@ -761,7 +770,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                 {
                 {
                     keyStoreFormat = HttpsServerCertificateManager.KeyStoreFormat.valueOf( pwmRequest.readParameterAsString( "format" ) );
                     keyStoreFormat = HttpsServerCertificateManager.KeyStoreFormat.valueOf( pwmRequest.readParameterAsString( "format" ) );
                 }
                 }
-                catch ( IllegalArgumentException e )
+                catch ( final IllegalArgumentException e )
                 {
                 {
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "unknown format type: " + e.getMessage(), new String[]
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "unknown format type: " + e.getMessage(), new String[]
                             {
                             {
@@ -774,7 +783,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                 final ByteArrayInputStream fileIs = new ByteArrayInputStream( fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).getContent().copyOf() );
                 final ByteArrayInputStream fileIs = new ByteArrayInputStream( fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).getContent().copyOf() );
 
 
                 HttpsServerCertificateManager.importKey(
                 HttpsServerCertificateManager.importKey(
-                        configManagerBean.getStoredConfiguration(),
+                        modifier,
                         keyStoreFormat,
                         keyStoreFormat,
                         fileIs,
                         fileIs,
                         passwordData,
                         passwordData,
@@ -784,7 +793,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                 pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
                 pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
                 return ProcessStatus.Halt;
                 return ProcessStatus.Halt;
             }
             }
-            catch ( PwmException e )
+            catch ( final PwmException e )
             {
             {
                 LOGGER.error( pwmRequest, "error during https certificate upload: " + e.getMessage() );
                 LOGGER.error( pwmRequest, "error during https certificate upload: " + e.getMessage() );
                 pwmRequest.respondWithError( e.getErrorInformation(), false );
                 pwmRequest.respondWithError( e.getErrorInformation(), false );
@@ -799,10 +808,11 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                     ? pwmRequest.getPwmSession().getUserInfo().getUserIdentity()
                     ? pwmRequest.getPwmSession().getUserInfo().getUserIdentity()
                     : null;
                     : null;
 
 
-            configManagerBean.getStoredConfiguration().writeSetting( setting, fileValue, userIdentity );
+            modifier.writeSetting( setting, null, fileValue, userIdentity );
             pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
             pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         }
         }
 
 
+        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         return ProcessStatus.Halt;
         return ProcessStatus.Halt;
     }
     }
 
 
@@ -957,7 +967,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         }
         }
         {
         {
             final LinkedHashMap<String, Object> varMap = new LinkedHashMap<>();
             final LinkedHashMap<String, Object> varMap = new LinkedHashMap<>();
-            varMap.put( "ldapProfileIds", storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST ).toNativeObject() );
+            varMap.put( "ldapProfileIds", storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST, null ).toNativeObject() );
             varMap.put( "currentTemplate", storedConfiguration.getTemplateSet() );
             varMap.put( "currentTemplate", storedConfiguration.getTemplateSet() );
             varMap.put( "configurationNotes", storedConfiguration.readConfigProperty( ConfigurationProperty.NOTES ) );
             varMap.put( "configurationNotes", storedConfiguration.readConfigProperty( ConfigurationProperty.NOTES ) );
             returnMap.put( "var", varMap );
             returnMap.put( "var", varMap );
@@ -1043,6 +1053,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, ServletException, PwmUnrecoverableException
             throws IOException, ServletException, PwmUnrecoverableException
     {
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation );
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation );
 
 
         final String settingKey = inputMap.get( "setting" );
         final String settingKey = inputMap.get( "setting" );
@@ -1063,13 +1074,15 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final String destinationID = inputMap.get( "destinationID" );
         final String destinationID = inputMap.get( "destinationID" );
         try
         try
         {
         {
-            configManagerBean.getStoredConfiguration().copyProfileID( category, sourceID, destinationID, pwmRequest.getUserInfoIfLoggedIn() );
+            modifier.copyProfileID( category, sourceID, destinationID, pwmRequest.getUserInfoIfLoggedIn() );
             pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
             pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         }
         }
-        catch ( PwmUnrecoverableException e )
+        catch ( final PwmUnrecoverableException e )
         {
         {
             pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) );
             pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) );
         }
         }
+
+        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         return ProcessStatus.Halt;
         return ProcessStatus.Halt;
     }
     }
 
 

+ 6 - 15
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java

@@ -22,8 +22,7 @@ package password.pwm.http.servlet.configeditor;
 
 
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
-import password.pwm.config.stored.ComparingChangeLog;
-import password.pwm.config.stored.ConfigChangeLog;
+import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.FileValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
@@ -47,6 +46,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Set;
 
 
 public class ConfigEditorServletUtils
 public class ConfigEditorServletUtils
 {
 {
@@ -102,17 +102,15 @@ public class ConfigEditorServletUtils
             final Map<String, Object> outputMap
             final Map<String, Object> outputMap
     )
     )
     {
     {
-        try
-        {
             final Locale locale = pwmRequest.getLocale();
             final Locale locale = pwmRequest.getLocale();
 
 
-            final ConfigChangeLog changeLog = ComparingChangeLog.create(
+            final Set<StoredConfigItemKey> changeLog = StoredConfigurationUtil.changedValues(
                     pwmRequest.getPwmApplication().getConfig().getStoredConfiguration(),
                     pwmRequest.getPwmApplication().getConfig().getStoredConfiguration(),
                     configManagerBean.getStoredConfiguration() );
                     configManagerBean.getStoredConfiguration() );
 
 
-            final Map<String, String> changeLogMap = StoredConfigurationUtil.asDebugMap(
+            final Map<String, String> changeLogMap = StoredConfigurationUtil.makeDebugMap(
                     configManagerBean.getStoredConfiguration(),
                     configManagerBean.getStoredConfiguration(),
-                    changeLog.changedValues(),
+                    changeLog,
                     locale );
                     locale );
 
 
             final StringBuilder output = new StringBuilder();
             final StringBuilder output = new StringBuilder();
@@ -132,15 +130,8 @@ public class ConfigEditorServletUtils
                 }
                 }
             }
             }
             outputMap.put( "html", output.toString() );
             outputMap.put( "html", output.toString() );
-            outputMap.put( "modified", changeLog.isModified() );
-        }
-        catch ( PwmUnrecoverableException e )
-        {
+            outputMap.put( "modified", !changeLog.isEmpty() );
 
 
-            final String msg = "error generating change log html: " + e.getMessage();
-            LOGGER.error( msg, e );
-            outputMap.put( "html", msg );
-        }
     }
     }
 
 
     static HealthData configurationHealth(
     static HealthData configurationHealth(

+ 7 - 6
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java

@@ -25,6 +25,7 @@ import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.ChallengeValue;
 import password.pwm.config.value.ChallengeValue;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.FileValue;
@@ -70,7 +71,7 @@ public class ConfigGuideForm
 
 
     private static void updateStoredConfigTemplateValue(
     private static void updateStoredConfigTemplateValue(
             final Map<ConfigGuideFormField, String> formData,
             final Map<ConfigGuideFormField, String> formData,
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier modifier,
             final PwmSetting pwmSetting,
             final PwmSetting pwmSetting,
             final ConfigGuideFormField formField,
             final ConfigGuideFormField formField,
             final PwmSettingTemplate.Type type
             final PwmSettingTemplate.Type type
@@ -81,7 +82,7 @@ public class ConfigGuideForm
         if ( !StringUtil.isEmpty( formValue ) )
         if ( !StringUtil.isEmpty( formValue ) )
         {
         {
             final PwmSettingTemplate template = PwmSettingTemplate.templateForString( formValue, type );
             final PwmSettingTemplate template = PwmSettingTemplate.templateForString( formValue, type );
-            storedConfiguration.writeSetting( pwmSetting, null, new StringValue( template.toString() ), null );
+            modifier.writeSetting( pwmSetting, null, new StringValue( template.toString() ), null );
         }
         }
     }
     }
 
 
@@ -94,7 +95,7 @@ public class ConfigGuideForm
     {
     {
 
 
         final Map<ConfigGuideFormField, String> formData = configGuideBean.getFormData();
         final Map<ConfigGuideFormField, String> formData = configGuideBean.getFormData();
-        final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newStoredConfiguration();
+        final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( StoredConfigurationFactory.newConfig() );
 
 
         // templates
         // templates
         updateStoredConfigTemplateValue(
         updateStoredConfigTemplateValue(
@@ -175,7 +176,7 @@ public class ConfigGuideForm
                     .type( UserPermission.Type.ldapGroup )
                     .type( UserPermission.Type.ldapGroup )
                     .ldapBase( groupDN )
                     .ldapBase( groupDN )
                     .build() );
                     .build() );
-            storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, new UserPermissionValue( userPermissions ), null );
+            storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, null, new UserPermissionValue( userPermissions ), null );
         }
         }
 
 
         {
         {
@@ -219,12 +220,12 @@ public class ConfigGuideForm
         }
         }
 
 
         // set site url
         // set site url
-        storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, new StringValue( formData.get( ConfigGuideFormField.PARAM_APP_SITEURL ) ), null );
+        storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, null, new StringValue( formData.get( ConfigGuideFormField.PARAM_APP_SITEURL ) ), null );
 
 
         // enable debug mode
         // enable debug mode
         storedConfiguration.writeSetting( PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS, null, new BooleanValue( true ), null );
         storedConfiguration.writeSetting( PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS, null, new BooleanValue( true ), null );
 
 
-        return storedConfiguration;
+        return storedConfiguration.newStoredConfiguration();
     }
     }
 
 
     static String figureLdapUrlFromFormConfig( final Map<ConfigGuideFormField, String> ldapForm )
     static String figureLdapUrlFromFormConfig( final Map<ConfigGuideFormField, String> ldapForm )

+ 10 - 6
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -35,6 +35,7 @@ import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.config.value.ValueFactory;
@@ -359,7 +360,8 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         {
         {
             final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
             final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
             final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
             final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
-            final Serializable output = userMatchViewerFunction.provideFunction( pwmRequest, storedConfiguration, PwmSetting.QUERY_MATCH_PWM_ADMIN, null, null );
+            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+            final Serializable output = userMatchViewerFunction.provideFunction( pwmRequest, modifier, PwmSetting.QUERY_MATCH_PWM_ADMIN, null, null );
             pwmRequest.outputJsonResult( RestResultBean.withData( output ) );
             pwmRequest.outputJsonResult( RestResultBean.withData( output ) );
         }
         }
         catch ( PwmException e )
         catch ( PwmException e )
@@ -384,11 +386,13 @@ public class ConfigGuideServlet extends ControlledPwmServlet
     {
     {
         final ConfigGuideBean configGuideBean = getBean( pwmRequest );
         final ConfigGuideBean configGuideBean = getBean( pwmRequest );
 
 
-        final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
+        StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
         if ( configGuideBean.getStep() == GuideStep.LDAP_PROXY )
         if ( configGuideBean.getStep() == GuideStep.LDAP_PROXY )
         {
         {
-            storedConfiguration.resetSetting( PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_KEY, null );
-            storedConfiguration.resetSetting( PwmSetting.LDAP_PROXY_USER_PASSWORD, LDAP_PROFILE_KEY, null );
+            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+            modifier.resetSetting( PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_KEY, null );
+            modifier.resetSetting( PwmSetting.LDAP_PROXY_USER_PASSWORD, LDAP_PROFILE_KEY, null );
+            storedConfiguration = modifier.newStoredConfiguration();
         }
         }
 
 
         final Instant startTime = Instant.now();
         final Instant startTime = Instant.now();
@@ -573,10 +577,10 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         final ContextManager contextManager = ContextManager.getContextManager( pwmRequest );
         final ContextManager contextManager = ContextManager.getContextManager( pwmRequest );
         try
         try
         {
         {
-            final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newStoredConfiguration();
+            final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( StoredConfigurationFactory.newConfig() );
             storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "true" );
             storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "true" );
             StoredConfigurationUtil.setPassword( storedConfiguration, password );
             StoredConfigurationUtil.setPassword( storedConfiguration, password );
-            ConfigGuideUtils.writeConfig( contextManager, storedConfiguration );
+            ConfigGuideUtils.writeConfig( contextManager, storedConfiguration.newStoredConfiguration() );
             pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
             pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
             pwmRequest.invalidateSession();
             pwmRequest.invalidateSession();
         }
         }

+ 10 - 7
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java

@@ -32,6 +32,7 @@ import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -72,9 +73,10 @@ public class ConfigGuideUtils
     static void writeConfig(
     static void writeConfig(
             final ContextManager contextManager,
             final ContextManager contextManager,
             final ConfigGuideBean configGuideBean
             final ConfigGuideBean configGuideBean
-    ) throws PwmOperationalException, PwmUnrecoverableException
+    )
+            throws PwmOperationalException, PwmUnrecoverableException
     {
     {
-        final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
+        final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( ConfigGuideForm.generateStoredConfig( configGuideBean ) );
         final String configPassword = configGuideBean.getFormData().get( ConfigGuideFormField.PARAM_CONFIG_PASSWORD );
         final String configPassword = configGuideBean.getFormData().get( ConfigGuideFormField.PARAM_CONFIG_PASSWORD );
         if ( configPassword != null && configPassword.length() > 0 )
         if ( configPassword != null && configPassword.length() > 0 )
         {
         {
@@ -86,7 +88,7 @@ public class ConfigGuideUtils
         }
         }
 
 
         storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "false" );
         storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "false" );
-        ConfigGuideUtils.writeConfig( contextManager, storedConfiguration );
+        ConfigGuideUtils.writeConfig( contextManager, storedConfiguration.newStoredConfiguration() );
     }
     }
 
 
     static void writeConfig(
     static void writeConfig(
@@ -100,18 +102,19 @@ public class ConfigGuideUtils
 
 
         try
         try
         {
         {
+            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
             // add a random security key
             // add a random security key
-            StoredConfigurationUtil.initNewRandomSecurityKey( storedConfiguration );
+            StoredConfigurationUtil.initNewRandomSecurityKey( modifier );
 
 
-            configReader.saveConfiguration( storedConfiguration, pwmApplication, null );
+            configReader.saveConfiguration( modifier.newStoredConfiguration(), pwmApplication, null );
 
 
             contextManager.requestPwmApplicationRestart();
             contextManager.requestPwmApplicationRestart();
         }
         }
-        catch ( PwmException e )
+        catch ( final PwmException e )
         {
         {
             throw new PwmOperationalException( e.getErrorInformation() );
             throw new PwmOperationalException( e.getErrorInformation() );
         }
         }
-        catch ( Exception e )
+        catch ( final Exception e )
         {
         {
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, "unable to save configuration: " + e.getLocalizedMessage() );
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, "unable to save configuration: " + e.getLocalizedMessage() );
             throw new PwmOperationalException( errorInformation );
             throw new PwmOperationalException( errorInformation );

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

@@ -30,7 +30,6 @@ import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
-import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
@@ -54,6 +53,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
+import java.util.Set;
 
 
 @WebServlet(
 @WebServlet(
         name = "ConfigManagerCertificateServlet",
         name = "ConfigManagerCertificateServlet",
@@ -116,7 +116,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
     List<CertificateDebugDataItem> makeCertificateDebugData( final Configuration configuration ) throws PwmUnrecoverableException
     List<CertificateDebugDataItem> makeCertificateDebugData( final Configuration configuration ) throws PwmUnrecoverableException
     {
     {
         final StoredConfiguration storedConfiguration = configuration.getStoredConfiguration();
         final StoredConfiguration storedConfiguration = configuration.getStoredConfiguration();
-        final List<StoredConfigItemKey> modifiedSettings = StoredConfigurationUtil.modifiedItems( storedConfiguration );
+        final Set<StoredConfigItemKey> modifiedSettings = storedConfiguration.modifiedItems();
 
 
         final List<CertificateDebugDataItem> certificateDebugDataItems = new ArrayList<>();
         final List<CertificateDebugDataItem> certificateDebugDataItems = new ArrayList<>();
 
 
@@ -134,7 +134,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
                     }
                     }
                     else
                     else
                     {
                     {
-                        storedValue = storedConfiguration.readSetting( pwmSetting );
+                        storedValue = storedConfiguration.readSetting( pwmSetting, null );
                     }
                     }
                     final X509Certificate[] arrayCerts = ( X509Certificate[] ) storedValue.toNativeObject();
                     final X509Certificate[] arrayCerts = ( X509Certificate[] ) storedValue.toNativeObject();
                     final List<X509Certificate> certificates = arrayCerts == null ? Collections.emptyList() : Arrays.asList( arrayCerts );
                     final List<X509Certificate> certificates = arrayCerts == null ? Collections.emptyList() : Arrays.asList( arrayCerts );
@@ -149,7 +149,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
                     }
                     }
                     else
                     else
                     {
                     {
-                        storedValue = storedConfiguration.readSetting( pwmSetting );
+                        storedValue = storedConfiguration.readSetting( pwmSetting, null );
                     }
                     }
                     final List<ActionConfiguration> actionConfigurations = ( List ) storedValue.toNativeObject();
                     final List<ActionConfiguration> actionConfigurations = ( List ) storedValue.toNativeObject();
                     for ( final ActionConfiguration actionConfiguration : actionConfigurations )
                     for ( final ActionConfiguration actionConfiguration : actionConfigurations )

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

@@ -27,11 +27,12 @@ import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
-import password.pwm.config.stored.ComparingChangeLog;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -76,6 +77,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Set;
 import java.util.zip.ZipOutputStream;
 import java.util.zip.ZipOutputStream;
 
 
 @WebServlet(
 @WebServlet(
@@ -269,10 +271,11 @@ public class ConfigManagerServlet extends AbstractPwmServlet
                 return;
                 return;
             }
             }
 
 
-            storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "false" );
-            saveConfiguration( pwmRequest, storedConfiguration );
+            final StoredConfigurationModifier modifiedConfig = StoredConfigurationModifier.newModifier( storedConfiguration );
+            modifiedConfig.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "false" );
+            saveConfiguration( pwmRequest, modifiedConfig.newStoredConfiguration() );
             final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
             final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
-            configManagerBean.setConfiguration( null );
+            configManagerBean.setStoredConfiguration( null );
         }
         }
         catch ( PwmException e )
         catch ( PwmException e )
         {
         {
@@ -326,10 +329,12 @@ public class ConfigManagerServlet extends AbstractPwmServlet
             final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
             final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
             if ( pwmApplication.getAuditManager() != null && pwmApplication.getAuditManager().status() == PwmService.STATUS.OPEN )
             if ( pwmApplication.getAuditManager() != null && pwmApplication.getAuditManager().status() == PwmService.STATUS.OPEN )
             {
             {
-                final ComparingChangeLog comparingChangeLog = ComparingChangeLog.create( pwmApplication.getConfig().getStoredConfiguration(), storedConfiguration );
+                final Set<StoredConfigItemKey> configurationDifferential = StoredConfigurationUtil.changedValues(
+                        pwmApplication.getConfig().getStoredConfiguration(),
+                        storedConfiguration );
                 final String modifyMessage = "Configuration Changes: " + StoredConfigurationUtil.changeLogAsDebugString(
                 final String modifyMessage = "Configuration Changes: " + StoredConfigurationUtil.changeLogAsDebugString(
                         storedConfiguration,
                         storedConfiguration,
-                        comparingChangeLog,
+                        configurationDifferential,
                         PwmConstants.DEFAULT_LOCALE
                         PwmConstants.DEFAULT_LOCALE
                 );
                 );
                 final AuditRecord auditRecord = new AuditRecordFactory( pwmApplication ).createUserAuditRecord(
                 final AuditRecord auditRecord = new AuditRecordFactory( pwmApplication ).createUserAuditRecord(
@@ -404,17 +409,14 @@ public class ConfigManagerServlet extends AbstractPwmServlet
     public static StoredConfiguration readCurrentConfiguration( final PwmRequest pwmRequest )
     public static StoredConfiguration readCurrentConfiguration( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final ContextManager contextManager = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() );
-        final ConfigurationReader runningConfigReader = contextManager.getConfigReader();
-        final StoredConfiguration runningConfig = runningConfigReader.getStoredConfiguration();
-        return runningConfig.copy( );
+        return pwmRequest.getConfig().getStoredConfiguration();
     }
     }
 
 
     private void showSummary( final PwmRequest pwmRequest )
     private void showSummary( final PwmRequest pwmRequest )
             throws IOException, ServletException, PwmUnrecoverableException
             throws IOException, ServletException, PwmUnrecoverableException
     {
     {
         final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest );
         final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest );
-        final Map<String, String> outputMap = StoredConfigurationUtil.asDebugMap( storedConfiguration, storedConfiguration.modifiedSettings(), pwmRequest.getLocale() );
+        final Map<String, String> outputMap = StoredConfigurationUtil.makeDebugMap( storedConfiguration, storedConfiguration.modifiedItems(), pwmRequest.getLocale() );
         pwmRequest.setAttribute( PwmRequestAttribute.ConfigurationSummaryOutput, new LinkedHashMap<>( outputMap ) );
         pwmRequest.setAttribute( PwmRequestAttribute.ConfigurationSummaryOutput, new LinkedHashMap<>( outputMap ) );
         pwmRequest.forwardToJsp( JspUrl.CONFIG_MANAGER_EDITOR_SUMMARY );
         pwmRequest.forwardToJsp( JspUrl.CONFIG_MANAGER_EDITOR_SUMMARY );
     }
     }

+ 5 - 6
server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java

@@ -132,9 +132,8 @@ public class DebugItemGenerator
         this.pwmApplication = pwmApplication;
         this.pwmApplication = pwmApplication;
         this.sessionLabel = sessionLabel;
         this.sessionLabel = sessionLabel;
 
 
-        final StoredConfiguration storedConfiguration = pwmApplication.getConfig().getStoredConfiguration().copy();
-        StoredConfigurationUtil.resetAllPasswordValuesWithComment( storedConfiguration );
-        this.obfuscatedConfiguration = new Configuration( storedConfiguration );
+        final StoredConfiguration obfuscatedStoredConfig = StoredConfigurationUtil.copyConfigAndBlankAllPasswords( pwmApplication.getConfig().getStoredConfiguration() );
+        this.obfuscatedConfiguration = new Configuration( obfuscatedStoredConfig );
     }
     }
 
 
     private String getFilenameBase()
     private String getFilenameBase()
@@ -218,11 +217,11 @@ public class DebugItemGenerator
             final StoredConfiguration storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration();
             final StoredConfiguration storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration();
             final TreeMap<String, Object> outputObject = new TreeMap<>();
             final TreeMap<String, Object> outputObject = new TreeMap<>();
 
 
-            for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedSettings() )
+            for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedItems() )
             {
             {
                 if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
                 if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
                 {
                 {
-                    final String key = storedConfigItemKey.toString( PwmConstants.DEFAULT_LOCALE );
+                    final String key = storedConfigItemKey.getLabel( PwmConstants.DEFAULT_LOCALE );
                     final StoredValue value = storedConfiguration.readSetting( storedConfigItemKey.toPwmSetting(), storedConfigItemKey.getProfileID() );
                     final StoredValue value = storedConfiguration.readSetting( storedConfigItemKey.toPwmSetting(), storedConfigItemKey.getProfileID() );
                     outputObject.put( key, value );
                     outputObject.put( key, value );
                 }
                 }
@@ -255,7 +254,7 @@ public class DebugItemGenerator
             writer.write( "This file is " + PwmConstants.DEFAULT_CHARSET.displayName() + " encoded\n" );
             writer.write( "This file is " + PwmConstants.DEFAULT_CHARSET.displayName() + " encoded\n" );
 
 
             writer.write( "\n" );
             writer.write( "\n" );
-            final Set<StoredConfigItemKey> modifiedSettings = storedConfiguration.modifiedSettings();
+            final Set<StoredConfigItemKey> modifiedSettings = storedConfiguration.modifiedItems();
 
 
             for ( final StoredConfigItemKey storedConfigItemKey : modifiedSettings )
             for ( final StoredConfigItemKey storedConfigItemKey : modifiedSettings )
             {
             {

+ 12 - 5
server/src/main/java/password/pwm/i18n/PwmLocaleBundle.java

@@ -22,6 +22,7 @@ package password.pwm.i18n;
 
 
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
@@ -29,7 +30,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.ResourceBundle;
 import java.util.ResourceBundle;
 import java.util.Set;
 import java.util.Set;
@@ -74,9 +74,7 @@ public enum PwmLocaleBundle
     {
     {
         for ( final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.values() )
         for ( final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.values() )
         {
         {
-            if ( Objects.equals( key, pwmLocaleBundle.name() )
-                    || Objects.equals( key, pwmLocaleBundle.getKey() )
-            )
+            if ( StringUtil.caseIgnoreContains( pwmLocaleBundle.getLegacyKeys(), key ) )
             {
             {
                 return Optional.of( pwmLocaleBundle );
                 return Optional.of( pwmLocaleBundle );
             }
             }
@@ -87,7 +85,16 @@ public enum PwmLocaleBundle
 
 
     public String getKey()
     public String getKey()
     {
     {
-        return getTheClass().getName();
+        return getTheClass().getSimpleName();
+    }
+
+    public Set<String> getLegacyKeys()
+    {
+        return Collections.unmodifiableSet( new HashSet<>( Arrays.asList(
+                this.getTheClass().getSimpleName(),
+                this.getTheClass().getName(),
+                "password.pwm." + this.getTheClass().getSimpleName()
+        ) ) );
     }
     }
 
 
     public Set<String> getDisplayKeys( )
     public Set<String> getDisplayKeys( )

+ 1 - 1
server/src/main/java/password/pwm/svc/node/NodeMachine.java

@@ -78,7 +78,7 @@ class NodeMachine
     public List<NodeInfo> nodes( ) throws PwmUnrecoverableException
     public List<NodeInfo> nodes( ) throws PwmUnrecoverableException
     {
     {
         final Map<String, NodeInfo> returnObj = new TreeMap<>();
         final Map<String, NodeInfo> returnObj = new TreeMap<>();
-        final String configHash = pwmApplication.getConfig().configurationHash();
+        final String configHash = pwmApplication.getConfig().configurationHash( pwmApplication.getSecureService() );
         for ( final StoredNodeData storedNodeData : knownNodes.values() )
         for ( final StoredNodeData storedNodeData : knownNodes.values() )
         {
         {
             final boolean configMatch = configHash.equals( storedNodeData.getConfigHash() );
             final boolean configMatch = configHash.equals( storedNodeData.getConfigHash() );

+ 1 - 1
server/src/main/java/password/pwm/svc/node/StoredNodeData.java

@@ -47,7 +47,7 @@ class StoredNodeData implements Serializable
                 pwmApplication.getStartupTime(),
                 pwmApplication.getStartupTime(),
                 pwmApplication.getInstanceID(),
                 pwmApplication.getInstanceID(),
                 pwmApplication.getRuntimeNonce(),
                 pwmApplication.getRuntimeNonce(),
-                pwmApplication.getConfig().configurationHash()
+                pwmApplication.getConfig().configurationHash( pwmApplication.getSecureService() )
         );
         );
     }
     }
 }
 }

+ 2 - 2
server/src/main/java/password/pwm/svc/report/ReportSettings.java

@@ -23,7 +23,6 @@ package password.pwm.svc.report;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Value;
 import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
-import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.value.data.UserPermission;
@@ -31,6 +30,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.SecureEngine;
 import password.pwm.util.secure.SecureEngine;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
@@ -141,6 +141,6 @@ class ReportSettings implements Serializable
     String getSettingsHash( )
     String getSettingsHash( )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        return SecureEngine.hash( JsonUtil.serialize( this ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD );
+        return SecureEngine.hash( JsonUtil.serialize( this ), PwmHashAlgorithm.SHA512 );
     }
     }
 }
 }

+ 1 - 5
server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -37,6 +37,7 @@ import password.pwm.util.PwmScheduler;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.Percent;
 import password.pwm.util.java.Percent;
+import password.pwm.util.java.PwmCallable;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
@@ -392,11 +393,6 @@ abstract class AbstractWordlist implements Wordlist, PwmService
         executorService.execute( new InspectorJob() );
         executorService.execute( new InspectorJob() );
     }
     }
 
 
-    private interface PwmCallable
-    {
-        void call() throws PwmUnrecoverableException;
-    }
-
     private void cancelBackgroundAndRunImmediate( final PwmCallable runnable ) throws PwmUnrecoverableException
     private void cancelBackgroundAndRunImmediate( final PwmCallable runnable ) throws PwmUnrecoverableException
     {
     {
         inhibitBackgroundImportFlag.set( true );
         inhibitBackgroundImportFlag.set( true );

+ 7 - 7
server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java

@@ -241,7 +241,7 @@ public class LDAPPermissionCalculator implements Serializable
         {
         {
             case PEOPLE_SEARCH:
             case PEOPLE_SEARCH:
             {
             {
-                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.PEOPLE_SEARCH_ENABLE ).toNativeObject() )
+                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.PEOPLE_SEARCH_ENABLE, null ).toNativeObject() )
                 {
                 {
                     return Collections.emptyList();
                     return Collections.emptyList();
                 }
                 }
@@ -268,7 +268,7 @@ public class LDAPPermissionCalculator implements Serializable
 
 
             case GUEST:
             case GUEST:
             {
             {
-                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.GUEST_ENABLE ).toNativeObject() )
+                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.GUEST_ENABLE, null ).toNativeObject() )
                 {
                 {
                     return Collections.emptyList();
                     return Collections.emptyList();
                 }
                 }
@@ -279,7 +279,7 @@ public class LDAPPermissionCalculator implements Serializable
             case UPDATE_PROFILE:
             case UPDATE_PROFILE:
             case UPDATE_SETTINGS:
             case UPDATE_SETTINGS:
             {
             {
-                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.UPDATE_PROFILE_ENABLE ).toNativeObject() )
+                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.UPDATE_PROFILE_ENABLE, null ).toNativeObject() )
                 {
                 {
                     return Collections.emptyList();
                     return Collections.emptyList();
                 }
                 }
@@ -288,7 +288,7 @@ public class LDAPPermissionCalculator implements Serializable
 
 
             case FORGOTTEN_USERNAME:
             case FORGOTTEN_USERNAME:
             {
             {
-                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.FORGOTTEN_USERNAME_ENABLE ).toNativeObject() )
+                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.FORGOTTEN_USERNAME_ENABLE, null ).toNativeObject() )
                 {
                 {
                     return Collections.emptyList();
                     return Collections.emptyList();
                 }
                 }
@@ -299,7 +299,7 @@ public class LDAPPermissionCalculator implements Serializable
             case NEWUSER_PROFILE:
             case NEWUSER_PROFILE:
             case NEWUSER_SETTINGS:
             case NEWUSER_SETTINGS:
             {
             {
-                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.NEWUSER_ENABLE ).toNativeObject() )
+                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.NEWUSER_ENABLE, null ).toNativeObject() )
                 {
                 {
                     return Collections.emptyList();
                     return Collections.emptyList();
                 }
                 }
@@ -308,7 +308,7 @@ public class LDAPPermissionCalculator implements Serializable
 
 
             case ACTIVATION:
             case ACTIVATION:
             {
             {
-                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.ACTIVATE_USER_ENABLE ).toNativeObject() )
+                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.ACTIVATE_USER_ENABLE, null ).toNativeObject() )
                 {
                 {
                     return Collections.emptyList();
                     return Collections.emptyList();
                 }
                 }
@@ -317,7 +317,7 @@ public class LDAPPermissionCalculator implements Serializable
 
 
             case HELPDESK_PROFILE:
             case HELPDESK_PROFILE:
             {
             {
-                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.HELPDESK_ENABLE ).toNativeObject() )
+                if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.HELPDESK_ENABLE, null ).toNativeObject() )
                 {
                 {
                     return Collections.emptyList();
                     return Collections.emptyList();
                 }
                 }

+ 24 - 23
server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java

@@ -25,6 +25,7 @@ import password.pwm.config.StoredValue;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PasswordValue;
@@ -116,7 +117,7 @@ public class PropertyConfigurationImporter
     {
     {
         readInputFile( propertiesInput );
         readInputFile( propertiesInput );
 
 
-        final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newStoredConfiguration( );
+        final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( StoredConfigurationFactory.newConfig( ) );
         StoredConfigurationUtil.initNewRandomSecurityKey( storedConfiguration );
         StoredConfigurationUtil.initNewRandomSecurityKey( storedConfiguration );
         storedConfiguration.writeConfigProperty(
         storedConfiguration.writeConfigProperty(
                 ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) );
                 ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) );
@@ -126,25 +127,25 @@ public class PropertyConfigurationImporter
                 ConfigurationProperty.IMPORT_LDAP_CERTIFICATES, Boolean.toString( true ) );
                 ConfigurationProperty.IMPORT_LDAP_CERTIFICATES, Boolean.toString( true ) );
 
 
         // static values
         // static values
-        storedConfiguration.writeSetting( PwmSetting.TEMPLATE_LDAP, new StringValue(
+        storedConfiguration.writeSetting( PwmSetting.TEMPLATE_LDAP, null, new StringValue(
                         inputMap.getOrDefault( PropertyKey.TEMPLATE_LDAP.name( ), PropertyKey.TEMPLATE_LDAP.getDefaultValue() ) ),
                         inputMap.getOrDefault( PropertyKey.TEMPLATE_LDAP.name( ), PropertyKey.TEMPLATE_LDAP.getDefaultValue() ) ),
                 null );
                 null );
 
 
         if ( inputMap.containsKey( PropertyKey.DISPLAY_THEME.name( ) ) )
         if ( inputMap.containsKey( PropertyKey.DISPLAY_THEME.name( ) ) )
         {
         {
-            storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, new StringValue(
+            storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, null, new StringValue(
                             inputMap.get( PropertyKey.DISPLAY_THEME.name( ) ) ),
                             inputMap.get( PropertyKey.DISPLAY_THEME.name( ) ) ),
                     null );
                     null );
         }
         }
 
 
-        storedConfiguration.writeSetting( PwmSetting.DISPLAY_HOME_BUTTON, new BooleanValue( false ), null );
-        storedConfiguration.writeSetting( PwmSetting.LOGOUT_AFTER_PASSWORD_CHANGE, new BooleanValue( false ), null );
-        storedConfiguration.writeSetting( PwmSetting.PASSWORD_REQUIRE_CURRENT, new BooleanValue( false ), null );
-        storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, new StringValue( "LDAP" ), null );
-        storedConfiguration.writeSetting( PwmSetting.CERTIFICATE_VALIDATION_MODE, new StringValue( "CA_ONLY" ), null );
+        storedConfiguration.writeSetting( PwmSetting.DISPLAY_HOME_BUTTON, null, new BooleanValue( false ), null );
+        storedConfiguration.writeSetting( PwmSetting.LOGOUT_AFTER_PASSWORD_CHANGE, null, new BooleanValue( false ), null );
+        storedConfiguration.writeSetting( PwmSetting.PASSWORD_REQUIRE_CURRENT, null, new BooleanValue( false ), null );
+        storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, null, new StringValue( "LDAP" ), null );
+        storedConfiguration.writeSetting( PwmSetting.CERTIFICATE_VALIDATION_MODE, null, new StringValue( "CA_ONLY" ), null );
         {
         {
             final String notes = "Configuration generated via properties import at " + JavaHelper.toIsoDate( Instant.now( ) );
             final String notes = "Configuration generated via properties import at " + JavaHelper.toIsoDate( Instant.now( ) );
-            storedConfiguration.writeSetting( PwmSetting.NOTES, new StringValue( notes ), null );
+            storedConfiguration.writeSetting( PwmSetting.NOTES, null, new StringValue( notes ), null );
         }
         }
 
 
         // ldap server
         // ldap server
@@ -157,22 +158,22 @@ public class PropertyConfigurationImporter
                 new StringArrayValue( Collections.singletonList( inputMap.get( PropertyKey.USER_CONTAINER.name( ) ) ) ), null );
                 new StringArrayValue( Collections.singletonList( inputMap.get( PropertyKey.USER_CONTAINER.name( ) ) ) ), null );
 
 
         // oauth
         // oauth
-        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_LOGIN_URL, new StringValue( makeOAuthBaseUrl( ) + "/grant" ), null );
-        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL, new StringValue( makeOAuthBaseUrl( ) + "/authcoderesolve" ), null );
-        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_ATTRIBUTES_URL, new StringValue( makeOAuthBaseUrl( ) + "/getattributes" ), null );
-        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CLIENTNAME, new StringValue( "sspr" ), null );
-        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_DN_ATTRIBUTE_NAME, new StringValue( "name" ), null );
-        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_SECRET,
+        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_LOGIN_URL, null, new StringValue( makeOAuthBaseUrl( ) + "/grant" ), null );
+        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL, null, new StringValue( makeOAuthBaseUrl( ) + "/authcoderesolve" ), null );
+        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_ATTRIBUTES_URL, null, new StringValue( makeOAuthBaseUrl( ) + "/getattributes" ), null );
+        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CLIENTNAME, null, new StringValue( "sspr" ), null );
+        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_DN_ATTRIBUTE_NAME, null, new StringValue( "name" ), null );
+        storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_SECRET, null,
                 new PasswordValue( PasswordData.forStringValue( inputMap.get( PropertyKey.SSO_SERVICE_PWD.name( ) ) ) ), null );
                 new PasswordValue( PasswordData.forStringValue( inputMap.get( PropertyKey.SSO_SERVICE_PWD.name( ) ) ) ), null );
 
 
         //urls
         //urls
-        storedConfiguration.writeSetting( PwmSetting.URL_FORWARD, makeForwardUrl( ), null );
-        storedConfiguration.writeSetting( PwmSetting.URL_LOGOUT, makeLogoutUrl( ), null );
-        storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, makeSelfUrl( ), null );
-        storedConfiguration.writeSetting( PwmSetting.SECURITY_REDIRECT_WHITELIST, makeWhitelistUrl( ), null );
+        storedConfiguration.writeSetting( PwmSetting.URL_FORWARD, null, makeForwardUrl( ), null );
+        storedConfiguration.writeSetting( PwmSetting.URL_LOGOUT, null, makeLogoutUrl( ), null );
+        storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, null, makeSelfUrl( ), null );
+        storedConfiguration.writeSetting( PwmSetting.SECURITY_REDIRECT_WHITELIST, null, makeWhitelistUrl( ), null );
 
 
         // admin settings
         // admin settings
-        storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, makeAdminPermissions( ), null );
+        storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, null, makeAdminPermissions( ), null );
         StoredConfigurationUtil.setPassword( storedConfiguration, inputMap.get( PropertyKey.CONFIGURATION_PWD.name( ) ) );
         StoredConfigurationUtil.setPassword( storedConfiguration, inputMap.get( PropertyKey.CONFIGURATION_PWD.name( ) ) );
 
 
         // certificates
         // certificates
@@ -187,19 +188,19 @@ public class PropertyConfigurationImporter
             final Optional<Collection<X509Certificate>> optionalCert = readCertificate( PropertyKey.AUDIT_SERVERCERTS );
             final Optional<Collection<X509Certificate>> optionalCert = readCertificate( PropertyKey.AUDIT_SERVERCERTS );
             if ( optionalCert.isPresent( ) )
             if ( optionalCert.isPresent( ) )
             {
             {
-                storedConfiguration.writeSetting( PwmSetting.AUDIT_SYSLOG_CERTIFICATES, new X509CertificateValue( optionalCert.get( ) ), null );
+                storedConfiguration.writeSetting( PwmSetting.AUDIT_SYSLOG_CERTIFICATES, null, new X509CertificateValue( optionalCert.get( ) ), null );
             }
             }
         }
         }
         {
         {
             final Optional<Collection<X509Certificate>> optionalCert = readCertificate( PropertyKey.OAUTH_IDSERVER_SERVERCERTS );
             final Optional<Collection<X509Certificate>> optionalCert = readCertificate( PropertyKey.OAUTH_IDSERVER_SERVERCERTS );
             if ( optionalCert.isPresent( ) )
             if ( optionalCert.isPresent( ) )
             {
             {
-                storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CERTIFICATE, new X509CertificateValue( optionalCert.get( ) ), null );
+                storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CERTIFICATE, null, new X509CertificateValue( optionalCert.get( ) ), null );
             }
             }
         }
         }
 
 
 
 
-        return storedConfiguration;
+        return storedConfiguration.newStoredConfiguration();
     }
     }
 
 
     private String makeOAuthBaseUrl( )
     private String makeOAuthBaseUrl( )

+ 4 - 2
server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java

@@ -24,6 +24,7 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
 
 
 import java.util.Optional;
 import java.util.Optional;
@@ -42,8 +43,9 @@ public class ConfigLockCommand extends AbstractCliCommand
             return;
             return;
         }
         }
 
 
-        storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) );
-        configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+        modifier.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) );
+        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: configuration has been locked" );
         out( "success: configuration has been locked" );
     }
     }
 
 

+ 1 - 1
server/src/main/java/password/pwm/util/cli/commands/ConfigNewCommand.java

@@ -33,7 +33,7 @@ public class ConfigNewCommand extends AbstractCliCommand
     public void doCommand( )
     public void doCommand( )
             throws Exception
             throws Exception
     {
     {
-        final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newStoredConfiguration();
+        final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newConfig();
 
 
         final File outputFile = ( File ) cliEnvironment.getOptions().get( CliParameters.REQUIRED_NEW_OUTPUT_FILE.getName() );
         final File outputFile = ( File ) cliEnvironment.getOptions().get( CliParameters.REQUIRED_NEW_OUTPUT_FILE.getName() );
 
 

+ 4 - 2
server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java

@@ -25,6 +25,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
 
 
 import java.io.File;
 import java.io.File;
@@ -50,11 +51,12 @@ public class ConfigResetHttpsCommand
         final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() );
         final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() );
         final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
         final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
 
 
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
         for ( final PwmSetting setting : PwmSettingCategory.HTTPS_SERVER.getSettings() )
         for ( final PwmSetting setting : PwmSettingCategory.HTTPS_SERVER.getSettings() )
         {
         {
-            storedConfiguration.resetSetting( setting, null, null );
+            modifier.resetSetting( setting, null, null );
         }
         }
-        configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success" );
         out( "success" );
     }
     }
 
 

+ 4 - 2
server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java

@@ -23,6 +23,7 @@ package password.pwm.util.cli.commands;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
 
 
@@ -36,9 +37,10 @@ public class ConfigSetPasswordCommand extends AbstractCliCommand
     {
     {
         final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
         final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader();
         final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
         final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
         final String password = getOptionalPassword();
         final String password = getOptionalPassword();
-        StoredConfigurationUtil.setPassword( storedConfiguration, password );
-        configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        StoredConfigurationUtil.setPassword( modifier, password );
+        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: new password has been set" );
         out( "success: new password has been set" );
     }
     }
 
 

+ 4 - 2
server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java

@@ -24,6 +24,7 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
 
 
 import java.util.Optional;
 import java.util.Optional;
@@ -43,8 +44,9 @@ public class ConfigUnlockCommand extends AbstractCliCommand
             return;
             return;
         }
         }
 
 
-        storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) );
-        configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+        modifier.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) );
+        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: configuration has been unlocked" );
         out( "success: configuration has been unlocked" );
     }
     }
 
 

+ 5 - 3
server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java

@@ -23,6 +23,7 @@ package password.pwm.util.cli.commands;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
@@ -65,24 +66,25 @@ public class ImportHttpsKeyStoreCommand extends AbstractCliCommand
 
 
         final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() );
         final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() );
         final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
         final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration();
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
 
 
         try ( FileInputStream fileInputStream = new FileInputStream( inputFile ) )
         try ( FileInputStream fileInputStream = new FileInputStream( inputFile ) )
         {
         {
             HttpsServerCertificateManager.importKey(
             HttpsServerCertificateManager.importKey(
-                    storedConfiguration,
+                    modifier,
                     format,
                     format,
                     fileInputStream,
                     fileInputStream,
                     new PasswordData( keyStorePassword ),
                     new PasswordData( keyStorePassword ),
                     inputAliasName
                     inputAliasName
             );
             );
         }
         }
-        catch ( Exception e )
+        catch ( final Exception e )
         {
         {
             out( "unable to load configured https certificate: " + e.getMessage() );
             out( "unable to load configured https certificate: " + e.getMessage() );
             return;
             return;
         }
         }
 
 
-        configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
+        configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL );
         out( "success: keystore has been imported" );
         out( "success: keystore has been imported" );
     }
     }
 
 

+ 3 - 7
server/src/main/java/password/pwm/config/stored/ConfigChangeLog.java → server/src/main/java/password/pwm/util/java/PwmCallable.java

@@ -18,15 +18,11 @@
  * limitations under the License.
  * limitations under the License.
  */
  */
 
 
-package password.pwm.config.stored;
+package password.pwm.util.java;
 
 
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 
 
-import java.util.Collection;
-
-public interface ConfigChangeLog
+public interface PwmCallable
 {
 {
-    boolean isModified( ) throws PwmUnrecoverableException;
-
-    Collection<StoredConfigItemKey> changedValues( ) throws PwmUnrecoverableException;
+    void call() throws PwmUnrecoverableException;
 }
 }

+ 4 - 8
server/src/main/java/password/pwm/config/stored/StoredConfigurationSpi.java → server/src/main/java/password/pwm/util/java/PwmSupplier.java

@@ -18,15 +18,11 @@
  * limitations under the License.
  * limitations under the License.
  */
  */
 
 
-package password.pwm.config.stored;
+package password.pwm.util.java;
 
 
-import password.pwm.config.StoredValue;
+import password.pwm.error.PwmUnrecoverableException;
 
 
-import java.util.Optional;
-
-interface StoredConfigurationSpi extends StoredConfiguration
+public interface PwmSupplier<T>
 {
 {
-    Optional<ValueMetaData> readMetaData( StoredConfigItemKey storedConfigItemKey );
-
-    Optional<StoredValue> readStoredValue( StoredConfigItemKey storedConfigItemKey );
+    T get() throws PwmUnrecoverableException;
 }
 }

+ 30 - 3
server/src/main/java/password/pwm/util/java/StringUtil.java

@@ -40,6 +40,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
@@ -233,9 +234,7 @@ public abstract class StringUtil
 
 
     public static boolean nullSafeEquals( final String value1, final String value2 )
     public static boolean nullSafeEquals( final String value1, final String value2 )
     {
     {
-        return value1 == null
-                ? value2 == null
-                : value1.equals( value2 );
+        return Objects.equals( value1, value2 );
     }
     }
 
 
     public enum Base64Options
     public enum Base64Options
@@ -589,4 +588,32 @@ public abstract class StringUtil
         }
         }
         return output.toString();
         return output.toString();
     }
     }
+
+    public static boolean caseIgnoreContains( final Collection<String> collection, final String value )
+    {
+        if ( value == null || collection == null )
+        {
+            return false;
+        }
+
+        if ( collection.contains( value ) )
+        {
+            return true;
+        }
+
+        final String lcaseValue = value.toLowerCase();
+        for ( final String item : collection )
+        {
+            if ( item != null )
+            {
+                final String lcaseItem = item.toLowerCase();
+                if ( lcaseItem.equalsIgnoreCase( lcaseValue ) )
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
 }
 }

+ 15 - 0
server/src/main/java/password/pwm/util/logging/PwmLogManager.java

@@ -33,6 +33,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
+import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.localdb.LocalDBException;
@@ -112,6 +113,20 @@ public class PwmLogManager
         java.util.logging.Logger.getLogger( "org.glassfish.jersey" ).setLevel( java.util.logging.Level.SEVERE );
         java.util.logging.Logger.getLogger( "org.glassfish.jersey" ).setLevel( java.util.logging.Level.SEVERE );
     }
     }
 
 
+
+    public static void preInitConsoleLogLevel( final String pwmLogLevel )
+    {
+        try
+        {
+            initConsoleLogger( new Configuration( StoredConfigurationFactory.newConfig() ), pwmLogLevel );
+        }
+        catch ( final Exception e )
+        {
+            final String msg = "error pre-initializing logger: " + e.getMessage();
+            System.err.println( msg );
+        }
+    }
+
     private static void initConsoleLogger(
     private static void initConsoleLogger(
             final Configuration config,
             final Configuration config,
             final String consoleLogLevel
             final String consoleLogLevel

+ 1 - 1
server/src/main/java/password/pwm/util/secure/HmacAlgorithm.java

@@ -20,7 +20,7 @@
 
 
 package password.pwm.util.secure;
 package password.pwm.util.secure;
 
 
-enum HmacAlgorithm
+public enum HmacAlgorithm
 {
 {
     HMAC_SHA_256( "HmacSHA256", PwmSecurityKey.Type.HMAC_256, 32 ),
     HMAC_SHA_256( "HmacSHA256", PwmSecurityKey.Type.HMAC_256, 32 ),
     HMAC_SHA_512( "HmacSHA512", PwmSecurityKey.Type.HMAC_512, 64 ),;
     HMAC_SHA_512( "HmacSHA512", PwmSecurityKey.Type.HMAC_512, 64 ),;

+ 6 - 5
server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java

@@ -40,7 +40,7 @@ import password.pwm.bean.PrivateKeyCertificate;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
 import password.pwm.config.StoredValue;
-import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.PrivateKeyValue;
 import password.pwm.config.value.PrivateKeyValue;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -353,12 +353,13 @@ public class HttpsServerCertificateManager
     }
     }
 
 
     public static void importKey(
     public static void importKey(
-            final StoredConfiguration storedConfiguration,
+            final StoredConfigurationModifier storedConfiguration,
             final KeyStoreFormat keyStoreFormat,
             final KeyStoreFormat keyStoreFormat,
             final InputStream inputStream,
             final InputStream inputStream,
             final PasswordData password,
             final PasswordData password,
             final String alias
             final String alias
-    ) throws PwmUnrecoverableException
+    )
+            throws PwmUnrecoverableException
     {
     {
         final char[] charPassword = password == null ? new char[ 0 ] : password.getStringValue().toCharArray();
         final char[] charPassword = password == null ? new char[ 0 ] : password.getStringValue().toCharArray();
         final PrivateKeyCertificate privateKeyCertificate;
         final PrivateKeyCertificate privateKeyCertificate;
@@ -399,7 +400,7 @@ public class HttpsServerCertificateManager
             LOGGER.debug( () -> "importing certificate chain: " + JsonUtil.serializeCollection( X509Utils.makeDebugInfoMap( certificates ) ) );
             LOGGER.debug( () -> "importing certificate chain: " + JsonUtil.serializeCollection( X509Utils.makeDebugInfoMap( certificates ) ) );
             privateKeyCertificate = new PrivateKeyCertificate( certificates, key );
             privateKeyCertificate = new PrivateKeyCertificate( certificates, key );
         }
         }
-        catch ( Exception e )
+        catch ( final Exception e )
         {
         {
             final String errorMsg = "unable to load configured https certificate: " + e.getMessage();
             final String errorMsg = "unable to load configured https certificate: " + e.getMessage();
             final String[] errorDetail = new String[]
             final String[] errorDetail = new String[]
@@ -410,7 +411,7 @@ public class HttpsServerCertificateManager
         }
         }
 
 
         final StoredValue storedValue = new PrivateKeyValue( privateKeyCertificate );
         final StoredValue storedValue = new PrivateKeyValue( privateKeyCertificate );
-        storedConfiguration.writeSetting( PwmSetting.HTTPS_CERT, storedValue, null );
+        storedConfiguration.writeSetting( PwmSetting.HTTPS_CERT, null, storedValue, null );
     }
     }
 
 
 }
 }

+ 11 - 1
server/src/main/java/password/pwm/util/secure/SecureEngine.java

@@ -335,7 +335,17 @@ public class SecureEngine
         return JavaHelper.byteArrayToHexString( computeHashToBytes( is, algorithm ) );
         return JavaHelper.byteArrayToHexString( computeHashToBytes( is, algorithm ) );
     }
     }
 
 
-    private static byte[] computeHmacToBytes(
+    public static String hmac(
+            final HmacAlgorithm hmacAlgorithm,
+            final PwmSecurityKey pwmSecurityKey,
+            final String input
+    )
+            throws PwmUnrecoverableException
+    {
+        return JavaHelper.byteArrayToHexString( computeHmacToBytes( hmacAlgorithm, pwmSecurityKey, input.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
+    }
+
+    public static byte[] computeHmacToBytes(
             final HmacAlgorithm hmacAlgorithm,
             final HmacAlgorithm hmacAlgorithm,
             final PwmSecurityKey pwmSecurityKey,
             final PwmSecurityKey pwmSecurityKey,
             final byte[] input
             final byte[] input

+ 8 - 1
server/src/test/java/password/pwm/config/PwmSettingTest.java

@@ -21,7 +21,10 @@
 package password.pwm.config;
 package password.pwm.config;
 
 
 import org.junit.Assert;
 import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.config.stored.XmlOutputProcessData;
@@ -32,6 +35,7 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.XmlDocument;
 import password.pwm.util.java.XmlDocument;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlFactory;
 import password.pwm.util.java.XmlFactory;
+import password.pwm.util.localdb.TestHelper;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 
 
 import java.io.InputStream;
 import java.io.InputStream;
@@ -43,10 +47,13 @@ import java.util.Set;
 
 
 public class PwmSettingTest
 public class PwmSettingTest
 {
 {
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
 
 
     @Test
     @Test
-    public void testDefaultValues() throws PwmUnrecoverableException, PwmOperationalException
+    public void testDefaultValues() throws Exception
     {
     {
+        final PwmApplication pwmApplication = TestHelper.makeTestPwmApplication( temporaryFolder.newFolder() );
         final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( "abcdefghijklmnopqrstuvwxyz" );
         final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( "abcdefghijklmnopqrstuvwxyz" );
         final XmlOutputProcessData outputSettings = XmlOutputProcessData.builder()
         final XmlOutputProcessData outputSettings = XmlOutputProcessData.builder()
                 .pwmSecurityKey( pwmSecurityKey )
                 .pwmSecurityKey( pwmSecurityKey )

+ 2 - 2
server/src/test/java/password/pwm/config/option/IdentityVerificationMethodEnumTest.java

@@ -31,7 +31,7 @@ public class IdentityVerificationMethodEnumTest
     @Test
     @Test
     public void testLabels() throws PwmUnrecoverableException
     public void testLabels() throws PwmUnrecoverableException
     {
     {
-        final Configuration configuration = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration configuration = new Configuration( StoredConfigurationFactory.newConfig() );
         for ( final IdentityVerificationMethod method : IdentityVerificationMethod.values() )
         for ( final IdentityVerificationMethod method : IdentityVerificationMethod.values() )
         {
         {
             method.getLabel( configuration, PwmConstants.DEFAULT_LOCALE );
             method.getLabel( configuration, PwmConstants.DEFAULT_LOCALE );
@@ -41,7 +41,7 @@ public class IdentityVerificationMethodEnumTest
     @Test
     @Test
     public void testDescriptions() throws PwmUnrecoverableException
     public void testDescriptions() throws PwmUnrecoverableException
     {
     {
-        final Configuration configuration = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration configuration = new Configuration( StoredConfigurationFactory.newConfig() );
         for ( final IdentityVerificationMethod category : IdentityVerificationMethod.values() )
         for ( final IdentityVerificationMethod category : IdentityVerificationMethod.values() )
         {
         {
             category.getDescription( configuration, PwmConstants.DEFAULT_LOCALE );
             category.getDescription( configuration, PwmConstants.DEFAULT_LOCALE );

+ 1 - 1
server/src/test/java/password/pwm/health/HealthMessageTest.java

@@ -49,7 +49,7 @@ public class HealthMessageTest
     @Test
     @Test
     public void testHealthMessageDescription() throws PwmUnrecoverableException
     public void testHealthMessageDescription() throws PwmUnrecoverableException
     {
     {
-        final Configuration configuration = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration configuration = new Configuration( StoredConfigurationFactory.newConfig() );
         final Locale locale = PwmConstants.DEFAULT_LOCALE;
         final Locale locale = PwmConstants.DEFAULT_LOCALE;
         for ( final HealthMessage healthMessage : HealthMessage.values() )
         for ( final HealthMessage healthMessage : HealthMessage.values() )
         {
         {

+ 1 - 1
server/src/test/java/password/pwm/http/client/PwmHttpClientTest.java

@@ -62,7 +62,7 @@ public class PwmHttpClientTest
 
 
 
 
     // Create a few mock objects, in case they're needed by the tests
     // Create a few mock objects, in case they're needed by the tests
-    private Configuration configuration = Mockito.spy( new Configuration( StoredConfigurationFactory.newStoredConfiguration() ) );
+    private Configuration configuration = Mockito.spy( new Configuration( StoredConfigurationFactory.newConfig() ) );
 
 
     public PwmHttpClientTest() throws PwmUnrecoverableException
     public PwmHttpClientTest() throws PwmUnrecoverableException
     {
     {

+ 11 - 11
server/src/test/java/password/pwm/http/filter/RequestInitializationFilterTest.java

@@ -25,8 +25,8 @@ import org.junit.Test;
 import org.mockito.Mockito;
 import org.mockito.Mockito;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.BooleanValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpHeader;
@@ -39,7 +39,7 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTest()
     public void readUserNetworkAddressTest()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Configuration conf = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration conf = new Configuration( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
 
 
@@ -51,7 +51,7 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestBogus()
     public void readUserNetworkAddressTestBogus()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Configuration conf = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration conf = new Configuration( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1m" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1m" );
 
 
@@ -63,7 +63,7 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestXForward()
     public void readUserNetworkAddressTestXForward()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Configuration conf = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration conf = new Configuration( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2" );
@@ -76,7 +76,7 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestBogusXForward()
     public void readUserNetworkAddressTestBogusXForward()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Configuration conf = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration conf = new Configuration( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a" );
@@ -89,7 +89,7 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestMultipleXForward()
     public void readUserNetworkAddressTestMultipleXForward()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Configuration conf = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration conf = new Configuration( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2, 10.1.1.3" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2, 10.1.1.3" );
@@ -102,7 +102,7 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestMultipleBogusXForward()
     public void readUserNetworkAddressTestMultipleBogusXForward()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Configuration conf = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration conf = new Configuration( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a, 10.1.1.3" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a, 10.1.1.3" );
@@ -115,7 +115,7 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestIPv6()
     public void readUserNetworkAddressTestIPv6()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final Configuration conf = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        final Configuration conf = new Configuration( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a, 2001:0db8:85a3:0000:0000:8a2e:0370:7334" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a, 2001:0db8:85a3:0000:0000:8a2e:0370:7334" );
@@ -128,9 +128,9 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestDisabledXForwardedFor()
     public void readUserNetworkAddressTestDisabledXForwardedFor()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newStoredConfiguration();
-        storedConfiguration.writeSetting( PwmSetting.USE_X_FORWARDED_FOR_HEADER, new BooleanValue( false ), null );
-        final Configuration conf = new Configuration( storedConfiguration );
+        final StoredConfigurationModifier modifier = StoredConfigurationFactory.newModifiableConfig();
+        modifier.writeSetting( PwmSetting.USE_X_FORWARDED_FOR_HEADER, null, new BooleanValue( false ), null );
+        final Configuration conf = new Configuration( modifier.newStoredConfiguration() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2" );

+ 1 - 1
server/src/test/java/password/pwm/i18n/NonLocalizedKeyTest.java

@@ -57,7 +57,7 @@ public class NonLocalizedKeyTest
 
 
         // check non-default locales do NOT have value
         // check non-default locales do NOT have value
         {
         {
-            final Configuration configuration = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+            final Configuration configuration = new Configuration( StoredConfigurationFactory.newConfig() );
             final List<Locale> locales = configuration.getKnownLocales();
             final List<Locale> locales = configuration.getKnownLocales();
             for ( final Locale locale : locales )
             for ( final Locale locale : locales )
             {
             {

+ 1 - 1
server/src/test/java/password/pwm/svc/event/CEFAuditFormatterTest.java

@@ -49,7 +49,7 @@ public class CEFAuditFormatterTest
 
 
         final CEFAuditFormatter cefAuditFormatter = new CEFAuditFormatter();
         final CEFAuditFormatter cefAuditFormatter = new CEFAuditFormatter();
         final PwmApplication pwmApplication = Mockito.mock( PwmApplication.class );
         final PwmApplication pwmApplication = Mockito.mock( PwmApplication.class );
-        Mockito.when( pwmApplication.getConfig() ).thenReturn( new Configuration( StoredConfigurationFactory.newStoredConfiguration() ) );
+        Mockito.when( pwmApplication.getConfig() ).thenReturn( new Configuration( StoredConfigurationFactory.newConfig() ) );
         final String output = cefAuditFormatter.convertAuditRecordToMessage( pwmApplication, auditRecord );
         final String output = cefAuditFormatter.convertAuditRecordToMessage( pwmApplication, auditRecord );
         Assert.assertEquals( expectedOutput, output );
         Assert.assertEquals( expectedOutput, output );
     }
     }

+ 1 - 1
server/src/test/java/password/pwm/svc/event/JsonAuditFormatterTest.java

@@ -45,7 +45,7 @@ public class JsonAuditFormatterTest
         final String expectedOutput = PwmConstants.PWM_APP_NAME + " " + jsonInput;
         final String expectedOutput = PwmConstants.PWM_APP_NAME + " " + jsonInput;
         final AuditFormatter auditFormatter = new JsonAuditFormatter();
         final AuditFormatter auditFormatter = new JsonAuditFormatter();
         final PwmApplication pwmApplication = Mockito.mock( PwmApplication.class );
         final PwmApplication pwmApplication = Mockito.mock( PwmApplication.class );
-        Mockito.when( pwmApplication.getConfig() ).thenReturn( new Configuration( StoredConfigurationFactory.newStoredConfiguration() ) );
+        Mockito.when( pwmApplication.getConfig() ).thenReturn( new Configuration( StoredConfigurationFactory.newConfig() ) );
         final String output = auditFormatter.convertAuditRecordToMessage( pwmApplication, auditRecord );
         final String output = auditFormatter.convertAuditRecordToMessage( pwmApplication, auditRecord );
         Assert.assertEquals( expectedOutput, output );
         Assert.assertEquals( expectedOutput, output );
     }
     }

+ 3 - 3
server/src/test/java/password/pwm/svc/wordlist/WordlistServiceTest.java

@@ -113,7 +113,7 @@ public class WordlistServiceTest
     public void testCaseSensitiveWordlist()
     public void testCaseSensitiveWordlist()
             throws Exception
             throws Exception
     {
     {
-        final Configuration configuration = Mockito.spy( new Configuration( StoredConfigurationFactory.newStoredConfiguration() ) );
+        final Configuration configuration = Mockito.spy( new Configuration( StoredConfigurationFactory.newConfig() ) );
         Mockito.when( configuration.readSettingAsBoolean( PwmSetting.WORDLIST_CASE_SENSITIVE ) ).thenReturn( true );
         Mockito.when( configuration.readSettingAsBoolean( PwmSetting.WORDLIST_CASE_SENSITIVE ) ).thenReturn( true );
         final WordlistService wordlistService = makeWordlistService( configuration );
         final WordlistService wordlistService = makeWordlistService( configuration );
 
 
@@ -129,7 +129,7 @@ public class WordlistServiceTest
     public void testChunkedWords()
     public void testChunkedWords()
             throws Exception
             throws Exception
     {
     {
-        final Configuration configuration = Mockito.spy( new Configuration( StoredConfigurationFactory.newStoredConfiguration() ) );
+        final Configuration configuration = Mockito.spy( new Configuration( StoredConfigurationFactory.newConfig() ) );
         Mockito.when( configuration.readSettingAsLong( PwmSetting.PASSWORD_WORDLIST_WORDSIZE ) ).thenReturn( 4L );
         Mockito.when( configuration.readSettingAsLong( PwmSetting.PASSWORD_WORDLIST_WORDSIZE ) ).thenReturn( 4L );
         final WordlistService wordlistService = makeWordlistService( configuration );
         final WordlistService wordlistService = makeWordlistService( configuration );
 
 
@@ -149,7 +149,7 @@ public class WordlistServiceTest
     {
     {
 
 
         final Configuration configuration = inputConfiguration == null
         final Configuration configuration = inputConfiguration == null
-                ? Mockito.spy( new Configuration( StoredConfigurationFactory.newStoredConfiguration() ) )
+                ? Mockito.spy( new Configuration( StoredConfigurationFactory.newConfig() ) )
                 : inputConfiguration;
                 : inputConfiguration;
         Mockito.when( configuration.readAppProperty( AppProperty.WORDLIST_TEST_MODE ) ).thenReturn( "true" );
         Mockito.when( configuration.readAppProperty( AppProperty.WORDLIST_TEST_MODE ) ).thenReturn( "true" );
         Mockito.when( configuration.readSettingAsString( PwmSetting.WORDLIST_FILENAME ) ).thenReturn( "" );
         Mockito.when( configuration.readSettingAsString( PwmSetting.WORDLIST_FILENAME ) ).thenReturn( "" );

+ 1 - 1
server/src/test/java/password/pwm/util/LDAPPermissionCalculatorTest.java

@@ -31,7 +31,7 @@ public class LDAPPermissionCalculatorTest
     public void testDefaultPermissionCalculator()
     public void testDefaultPermissionCalculator()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final StoredConfiguration defaultConfig = StoredConfigurationFactory.newStoredConfiguration();
+        final StoredConfiguration defaultConfig = StoredConfigurationFactory.newConfig();
         final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator( defaultConfig );
         final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator( defaultConfig );
         ldapPermissionCalculator.getPermissionRecords();
         ldapPermissionCalculator.getPermissionRecords();
     }
     }

+ 1 - 1
server/src/test/java/password/pwm/util/localdb/LocalDBLoggerExtendedTest.java

@@ -81,7 +81,7 @@ public class LocalDBLoggerExtendedTest
     {
     {
         TestHelper.setupLogging();
         TestHelper.setupLogging();
         final File localDBPath = testFolder.newFolder( "localdb-logger-test" );
         final File localDBPath = testFolder.newFolder( "localdb-logger-test" );
-        config = new Configuration( StoredConfigurationFactory.newStoredConfiguration() );
+        config = new Configuration( StoredConfigurationFactory.newConfig() );
 
 
         localDB = LocalDBFactory.getInstance(
         localDB = LocalDBFactory.getInstance(
                 localDBPath,
                 localDBPath,

+ 5 - 3
server/src/test/java/password/pwm/util/localdb/TestHelper.java

@@ -33,6 +33,7 @@ import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
+import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogLevel;
@@ -59,9 +60,10 @@ public class TestHelper
     public static PwmApplication makeTestPwmApplication( final File tempFolder )
     public static PwmApplication makeTestPwmApplication( final File tempFolder )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newStoredConfiguration();
-        storedConfiguration.writeSetting( PwmSetting.EVENTS_JAVA_STDOUT_LEVEL, new StringValue( PwmLogLevel.FATAL.toString() ), null );
-        final Configuration configuration = new Configuration( storedConfiguration );
+        final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newConfig();
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+        modifier.writeSetting( PwmSetting.EVENTS_JAVA_STDOUT_LEVEL, null, new StringValue( PwmLogLevel.FATAL.toString() ), null );
+        final Configuration configuration = new Configuration( modifier.newStoredConfiguration() );
         return makeTestPwmApplication( tempFolder, configuration );
         return makeTestPwmApplication( tempFolder, configuration );
     }
     }
 
 

+ 1 - 1
server/src/test/java/password/pwm/util/macro/MacroTest.java

@@ -105,7 +105,7 @@ public class MacroTest
         {
         {
             final PwmApplication pwmApplication = Mockito.mock( PwmApplication.class );
             final PwmApplication pwmApplication = Mockito.mock( PwmApplication.class );
             Mockito.when( pwmApplication.getApplicationMode() ).thenReturn( PwmApplicationMode.RUNNING );
             Mockito.when( pwmApplication.getApplicationMode() ).thenReturn( PwmApplicationMode.RUNNING );
-            Mockito.when( pwmApplication.getConfig() ).thenReturn( new Configuration( StoredConfigurationFactory.newStoredConfiguration() ) );
+            Mockito.when( pwmApplication.getConfig() ).thenReturn( new Configuration( StoredConfigurationFactory.newConfig() ) );
 
 
             final UserInfo userInfo = Mockito.mock( UserInfo.class );
             final UserInfo userInfo = Mockito.mock( UserInfo.class );
             final UserIdentity userIdentity = new UserIdentity( userDN, "profile" );
             final UserIdentity userIdentity = new UserIdentity( userDN, "profile" );