entry : outputMap.entrySet() )
- {
- final String keyName = entry.getKey();
- final String value = entry.getValue();
- if ( asHtml )
- {
- output.append( "" );
- output.append( keyName );
- output.append( "
" );
- output.append( StringUtil.escapeHtml( value ) );
- output.append( "
" );
- }
- else
- {
- output.append( keyName );
- output.append( "\n" );
- output.append( " Value: " );
- output.append( value );
- output.append( "\n" );
- }
- }
- }
- return output.toString();
- }
-
- @Override
- public void updateChangeLog( final StoredConfigReference reference, final StoredValue newValue )
- {
- changeLog.put( reference, newValue );
- originalValue.put( reference, null );
- }
-
- @Override
- public void updateChangeLog( final StoredConfigReference reference, final StoredValue currentValue, final StoredValue newValue )
- {
- if ( originalValue.containsKey( reference ) )
- {
- if ( newValue.equals( originalValue.get( reference ) ) )
- {
- originalValue.remove( reference );
- changeLog.remove( reference );
- }
- }
- else
- {
- originalValue.put( reference, currentValue );
- changeLog.put( reference, newValue );
- }
- }
-
- @Override
- public Collection changedValues( )
- {
- return changeLog.keySet();
- }
-}
diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigReference.java b/server/src/main/java/password/pwm/config/stored/ConfigRestartRequirement.java
similarity index 72%
rename from server/src/main/java/password/pwm/config/stored/StoredConfigReference.java
rename to server/src/main/java/password/pwm/config/stored/ConfigRestartRequirement.java
index b7b750548..0de077a23 100644
--- a/server/src/main/java/password/pwm/config/stored/StoredConfigReference.java
+++ b/server/src/main/java/password/pwm/config/stored/ConfigRestartRequirement.java
@@ -20,20 +20,7 @@
package password.pwm.config.stored;
-import java.io.Serializable;
-
-public interface StoredConfigReference extends Serializable, Comparable
+public enum ConfigRestartRequirement
{
- RecordType getRecordType( );
-
- String getRecordID( );
-
- String getProfileID( );
-
- enum RecordType
- {
- SETTING,
- LOCALE_BUNDLE,
- PROPERTY,
- }
+ Application
}
diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java b/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
index a9995d69a..7a0b71109 100644
--- a/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
+++ b/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
@@ -22,106 +22,130 @@
import password.pwm.PwmConstants;
import password.pwm.bean.UserIdentity;
+import password.pwm.config.Configuration;
import password.pwm.config.PwmSetting;
import password.pwm.config.StoredValue;
import password.pwm.config.option.ADPolicyComplexity;
+import password.pwm.config.option.RecoveryMinLifetimeOption;
import password.pwm.config.option.WebServiceUsage;
import password.pwm.config.value.OptionListValue;
+import password.pwm.config.value.StoredValueEncoder;
import password.pwm.config.value.StringArrayValue;
import password.pwm.config.value.StringValue;
import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.PwmExceptionLoggingConsumer;
+import password.pwm.util.java.StringUtil;
import password.pwm.util.java.XmlDocument;
import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmSecurityKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
class ConfigurationCleaner
{
private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigurationCleaner.class );
- private static final String NEW_PROFILE_NAME = "default";
- private final StoredConfigurationImpl storedConfiguration;
- private final XmlDocument document;
- ConfigurationCleaner(
- final StoredConfigurationImpl storedConfiguration, final XmlDocument document
- )
- {
- this.storedConfiguration = storedConfiguration;
- this.document = document;
- }
+ private static final List> XML_PRE_PROCESSORS = Collections.unmodifiableList( Arrays.asList(
+ new MigratePreValueXmlElements(),
+ new MigrateOldPropertyFormat(),
+ new AppPropertyOverrideMigration(),
+ new ProfileNonProfiledSettings(),
+ new MigrateDeprecatedProperties(),
+ new UpdatePropertiesWithoutType()
+ ) );
+ private static final List> STORED_CONFIG_POST_PROCESSORS = Collections.unmodifiableList( Arrays.asList(
+ new UpdateDeprecatedAdComplexitySettings(),
+ new UpdateDeprecatedMinPwdLifetimeSetting(),
+ new UpdateDeprecatedPublicHealthSetting()
+ ) );
- static void cleanup(
- final StoredConfigurationImpl storedConfiguration, final XmlDocument document
- )
- throws PwmUnrecoverableException
- {
- new ConfigurationCleaner( storedConfiguration, document ).cleanupImpl();
- }
- static void updateMandatoryElements(
- final StoredConfigurationImpl storedConfiguration,
+ static void preProcessXml(
final XmlDocument document
)
{
- new ConfigurationCleaner( storedConfiguration, document ).updateMandatoryElementsImpl();
+ XML_PRE_PROCESSORS.forEach( ( c ) -> PwmExceptionLoggingConsumer.wrapConsumer( c ).accept( document ) );
}
- private void cleanupImpl(
+ static void postProcessStoredConfig(
+ final StoredConfigurationModifier storedConfiguration
)
- throws PwmUnrecoverableException
{
- updateProperitiesWithoutType( );
- updateMandatoryElementsImpl();
- profilizeNonProfiledSettings( );
- stripOrphanedProfileSettings( );
- migrateAppProperties( );
- updateDeprecatedSettings( );
- migrateDeprecatedProperties( );
+ STORED_CONFIG_POST_PROCESSORS.forEach( aClass -> PwmExceptionLoggingConsumer.wrapConsumer( aClass ).accept( storedConfiguration ) );
}
-
- private void updateMandatoryElementsImpl( )
+ private static class MigratePreValueXmlElements implements PwmExceptionLoggingConsumer
{
- final XmlElement rootElement = document.getRootElement();
- rootElement.setComment( Collections.singletonList( generateCommentText() ) );
+ @Override
+ public void accept( final XmlDocument xmlDocument )
+ {
+ if ( readDocVersion( xmlDocument ) >= 4 )
+ {
+ return;
+ }
- rootElement.setAttribute( "pwmVersion", PwmConstants.BUILD_VERSION );
- rootElement.setAttribute( "pwmBuild", PwmConstants.BUILD_NUMBER );
- rootElement.setAttribute( "xmlVersion", StoredConfigurationImpl.XML_FORMAT_VERSION );
+ final List settingElements = xmlDocument.evaluateXpathToElements( "//"
+ + StoredConfigXmlConstants.XML_ELEMENT_SETTING );
+ for ( final XmlElement settingElement : settingElements )
+ {
+ final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE );
+ final Optional defaultElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT );
+ if ( valueElement.isPresent() && defaultElement.isPresent() )
+ {
+ final String textValue = settingElement.getTextTrim();
+ if ( !StringUtil.isEmpty( textValue ) )
+ {
+ final XmlElement newValueElement = XmlFactory.getFactory().newElement( StoredConfigXmlConstants.XML_ELEMENT_VALUE );
+ newValueElement.addText( textValue );
+ settingElement.addContent( newValueElement );
+ final String key = settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY );
+ LOGGER.info( () -> "migrating pre-xml 'value' tag format to use value element for key: " + key );
+ }
+ }
+ }
+ }
+ }
- // migrate old properties
+ private static class MigrateOldPropertyFormat implements PwmExceptionLoggingConsumer
+ {
+ @Override
+ public void accept( final XmlDocument xmlDocument )
{
-
// read correct (new) //properties[@type="config"]
- final String configPropertiesXpath = "//" + StoredConfigurationImpl.XML_ELEMENT_PROPERTIES
- + "[@" + StoredConfigurationImpl.XML_ATTRIBUTE_TYPE + "=\"" + StoredConfigurationImpl.XML_ATTRIBUTE_VALUE_CONFIG + "\"]";
- final XmlElement configPropertiesElement = document.evaluateXpathToElement( configPropertiesXpath );
+ final String configPropertiesXpath = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES
+ + "[@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + "=\"" + StoredConfigXmlConstants.XML_ATTRIBUTE_VALUE_CONFIG + "\"]";
+ final Optional configPropertiesElement = xmlDocument.evaluateXpathToElement( configPropertiesXpath );
// read list of old //properties[not (@type)]/property
- final String nonAttributedPropertyXpath = "//" + StoredConfigurationImpl.XML_ELEMENT_PROPERTIES
- + "[not (@" + StoredConfigurationImpl.XML_ATTRIBUTE_TYPE + ")]/" + StoredConfigurationImpl.XML_ELEMENT_PROPERTY;
- final List nonAttributedProperties = document.evaluateXpathToElements( nonAttributedPropertyXpath );
+ final String nonAttributedPropertyXpath = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES
+ + "[not (@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + ")]/" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTY;
+ final List nonAttributedProperties = xmlDocument.evaluateXpathToElements( nonAttributedPropertyXpath );
- if ( configPropertiesElement != null && nonAttributedProperties != null )
+ if ( configPropertiesElement.isPresent() && nonAttributedProperties != null )
{
for ( final XmlElement element : nonAttributedProperties )
{
element.detach();
- configPropertiesElement.addContent( element );
+ configPropertiesElement.get().addContent( element );
}
}
// remove old //properties[not (@type] element
- final String oldPropertiesXpath = "//" + StoredConfigurationImpl.XML_ELEMENT_PROPERTIES + "[not (@" + StoredConfigurationImpl.XML_ATTRIBUTE_TYPE + ")]";
- final List oldPropertiesElements = document.evaluateXpathToElements( oldPropertiesXpath );
+ final String oldPropertiesXpath = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES
+ + "[not (@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + ")]";
+ final List oldPropertiesElements = xmlDocument.evaluateXpathToElements( oldPropertiesXpath );
if ( oldPropertiesElements != null )
{
for ( final XmlElement element : oldPropertiesElements )
@@ -132,226 +156,260 @@ private void updateMandatoryElementsImpl( )
}
}
- private String generateCommentText( )
+ static class ProfileNonProfiledSettings implements PwmExceptionLoggingConsumer
{
- final StringBuilder commentText = new StringBuilder();
- commentText.append( "\t\t" ).append( " " ).append( "\n" );
- commentText.append( "\t\t" ).append( "This configuration file has been auto-generated by the " ).append( PwmConstants.PWM_APP_NAME )
- .append( " password self service application." ).append( "\n" );
- commentText.append( "\t\t" ).append( "" ).append( "\n" );
- commentText.append( "\t\t" ).append( "WARNING: This configuration file contains sensitive security information, please handle with care!" ).append( "\n" );
- commentText.append( "\t\t" ).append( "" ).append( "\n" );
- commentText.append( "\t\t" ).append( "WARNING: If a server is currently running using this configuration file, it will be restarted" ).append( "\n" );
- commentText.append( "\t\t" ).append( " and the configuration updated immediately when it is modified." ).append( "\n" );
- commentText.append( "\t\t" ).append( "" ).append( "\n" );
- commentText.append( "\t\t" ).append( "NOTICE: This file is encoded as UTF-8. Do not save or edit this file with an editor that does not" ).append( "\n" );
- commentText.append( "\t\t" ).append( " support UTF-8 encoding." ).append( "\n" );
- commentText.append( "\t\t" ).append( "" ).append( "\n" );
- commentText.append( "\t\t" ).append( "If unable to edit using the application ConfigurationEditor web UI, the following options are available." ).append( "\n" );
- commentText.append( "\t\t" ).append( " or 1. Edit this file directly by hand." ).append( "\n" );
- commentText.append( "\t\t" ).append( " or 2. Remove restrictions of the configuration by setting the property 'configIsEditable' to 'true' in this file. This will " )
- .append( "\n" );
- commentText.append( "\t\t" ).append( " allow access to the ConfigurationEditor web UI without having to authenticate to an LDAP server first." ).append( "\n" );
- commentText.append( "\t\t" ).append( " or 3. Remove restrictions of the configuration by using the the command line utility. " ).append( "\n" );
- commentText.append( "\t\t" ).append( "" ).append( "\n" );
- return commentText.toString();
- }
-
-
- private void profilizeNonProfiledSettings()
- throws PwmUnrecoverableException
- {
- for ( final PwmSetting setting : PwmSetting.values() )
+ @Override
+ public void accept( final XmlDocument xmlDocument )
{
- if ( setting.getCategory().hasProfiles() )
+ final StoredConfigurationFactory.XmlInputDocumentReader reader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument );
+ for ( final PwmSetting setting : PwmSetting.values() )
{
-
- final XmlElement settingElement = storedConfiguration.getXmlHelper().xpathForSetting( setting, null );
- if ( settingElement != null )
+ if ( setting.getCategory().hasProfiles() )
{
- settingElement.detach();
-
- final PwmSetting profileSetting = setting.getCategory().getProfileSetting();
- final List profileStringDefinitions = new ArrayList<>();
+ reader.xpathForSetting( setting, null ).ifPresent( existingSettingElement ->
{
- final StringArrayValue profileDefinitions = ( StringArrayValue ) storedConfiguration.readSetting( profileSetting );
- if ( profileDefinitions != null )
+ final List profileStringDefinitions = new ArrayList<>();
{
- if ( profileDefinitions.toNativeObject() != null )
+ final List configuredProfiles = reader.profilesForSetting( setting );
+ if ( !JavaHelper.isEmpty( configuredProfiles ) )
{
- profileStringDefinitions.addAll( profileDefinitions.toNativeObject() );
+ profileStringDefinitions.addAll( configuredProfiles );
}
}
- }
- if ( profileStringDefinitions.isEmpty() )
- {
- profileStringDefinitions.add( NEW_PROFILE_NAME );
- }
-
- final UserIdentity userIdentity = settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_MODIFY_USER ) != null
- ? UserIdentity.fromDelimitedKey( settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_MODIFY_USER ) )
- : null;
+ if ( profileStringDefinitions.isEmpty() )
+ {
+ profileStringDefinitions.add( PwmConstants.PROFILE_ID_DEFAULT );
+ }
- for ( final String destProfile : profileStringDefinitions )
- {
- LOGGER.info( () -> "moving setting " + setting.getKey() + " without profile attribute to profile \"" + destProfile + "\"." );
+ for ( final String destProfile : profileStringDefinitions )
{
- storedConfiguration.writeSetting( profileSetting, new StringArrayValue( profileStringDefinitions ), userIdentity );
+ LOGGER.info( () -> "moving setting " + setting.getKey() + " without profile attribute to profile \"" + destProfile + "\"." );
+ {
+ //existingSettingElement.detach();
+ final XmlElement newSettingElement = existingSettingElement.copy();
+ newSettingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE, destProfile );
+
+ final XmlElement settingsElement = reader.xpathForSettings();
+ settingsElement.addContent( newSettingElement );
+ }
}
- }
+ } );
}
}
}
}
- private void migrateDeprecatedProperties(
- )
- throws PwmUnrecoverableException
+ private static class MigrateDeprecatedProperties implements PwmExceptionLoggingConsumer
{
+ @Override
+ public void accept( final XmlDocument xmlDocument ) throws PwmUnrecoverableException
{
- final String xpathString = "//property[@key=\"" + ConfigurationProperty.LDAP_TEMPLATE.getKey() + "\"]";
- final List propertyElement = document.evaluateXpathToElements( xpathString );
- if ( propertyElement != null && !propertyElement.isEmpty() )
{
- final String value = propertyElement.get( 0 ).getText();
- storedConfiguration.writeSetting( PwmSetting.TEMPLATE_LDAP, new StringValue( value ), null );
- propertyElement.get( 0 ).detach();
+ final String xpathString = "//property[@key=\"" + ConfigurationProperty.LDAP_TEMPLATE.getKey() + "\"]";
+ final List propertyElement = xmlDocument.evaluateXpathToElements( xpathString );
+ if ( propertyElement != null && !propertyElement.isEmpty() )
+ {
+ final String value = propertyElement.get( 0 ).getText();
+ propertyElement.get( 0 ).detach();
+ attachStringSettingElement( xmlDocument, PwmSetting.TEMPLATE_LDAP, value );
+
+ }
}
- }
- {
- final String xpathString = "//property[@key=\"" + ConfigurationProperty.NOTES.getKey() + "\"]";
- final List propertyElement = document.evaluateXpathToElements( xpathString );
- if ( propertyElement != null && !propertyElement.isEmpty() )
{
- final String value = propertyElement.get( 0 ).getText();
- storedConfiguration.writeSetting( PwmSetting.NOTES, new StringValue( value ), null );
- propertyElement.get( 0 ).detach();
+ final String xpathString = "//property[@key=\"" + ConfigurationProperty.NOTES.getKey() + "\"]";
+ final List propertyElement = xmlDocument.evaluateXpathToElements( xpathString );
+ if ( propertyElement != null && !propertyElement.isEmpty() )
+ {
+ final String value = propertyElement.get( 0 ).getText();
+ propertyElement.get( 0 ).detach();
+ attachStringSettingElement( xmlDocument, PwmSetting.NOTES, value );
+ }
}
}
+
+ private static void attachStringSettingElement(
+ final XmlDocument xmlDocument,
+ final PwmSetting pwmSetting,
+ final String stringValue
+ )
+ throws PwmUnrecoverableException
+ {
+ final StoredConfigurationFactory.XmlInputDocumentReader inputDocumentReader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument );
+
+ final PwmSecurityKey pwmSecurityKey = inputDocumentReader.getKey();
+
+ final XmlElement settingElement = StoredConfigurationFactory.XmlOutputHandler.makeSettingXmlElement(
+ null,
+ pwmSetting,
+ null,
+ new StringValue( stringValue ),
+ XmlOutputProcessData.builder().storedValueEncoderMode( StoredValueEncoder.Mode.PLAIN ).pwmSecurityKey( pwmSecurityKey ).build() );
+ final Optional settingsElement = xmlDocument.getRootElement().getChild( StoredConfigXmlConstants.XML_ELEMENT_SETTING );
+ settingsElement.ifPresent( xmlElement -> xmlElement.addContent( settingElement ) );
+ }
}
- private void updateProperitiesWithoutType()
+ private static class UpdatePropertiesWithoutType implements PwmExceptionLoggingConsumer
{
- final String xpathString = "//properties[not(@type)]";
- final List propertiesElements = document.evaluateXpathToElements( xpathString );
- for ( final XmlElement propertiesElement : propertiesElements )
+ @Override
+ public void accept( final XmlDocument xmlDocument )
{
- propertiesElement.setAttribute( StoredConfigurationImpl.XML_ATTRIBUTE_TYPE, StoredConfigurationImpl.XML_ATTRIBUTE_VALUE_CONFIG );
+ final String xpathString = "//properties[not(@type)]";
+ final List propertiesElements = xmlDocument.evaluateXpathToElements( xpathString );
+ for ( final XmlElement propertiesElement : propertiesElements )
+ {
+ propertiesElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE, StoredConfigXmlConstants.XML_ATTRIBUTE_VALUE_CONFIG );
+ }
}
}
- private void stripOrphanedProfileSettings()
+ private static class AppPropertyOverrideMigration implements PwmExceptionLoggingConsumer
{
- for ( final PwmSetting setting : PwmSetting.values() )
+ @Override
+ public void accept( final XmlDocument xmlDocument ) throws PwmUnrecoverableException
{
- if ( setting.getCategory().hasProfiles() )
+ final StoredConfigurationFactory.XmlInputDocumentReader documentReader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument );
+ final List appPropertiesElements = documentReader.xpathForAppProperties();
+ for ( final XmlElement element : appPropertiesElements )
{
- final List validProfiles = storedConfiguration.profilesForSetting( setting );
- final String xpathString = "//setting[@key=\"" + setting.getKey() + "\"]";
- final List settingElements = document.evaluateXpathToElements( xpathString );
- for ( final XmlElement settingElement : settingElements )
+ final List properties = element.getChildren();
+ for ( final XmlElement property : properties )
{
- final String profileID = settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_PROFILE );
- if ( profileID != null )
+ final String key = property.getAttributeValue( "key" );
+ final String value = property.getText();
+ if ( key != null && !key.isEmpty() && value != null && !value.isEmpty() )
{
- if ( !validProfiles.contains( profileID ) )
+ LOGGER.info( () -> "migrating app-property config element '" + key + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey() );
+ final String newValue = key + "=" + value;
+
+ final List existingValues = new ArrayList<>();
{
- LOGGER.info( () -> "removing setting " + setting.getKey() + " with profile \"" + profileID + "\", profile is not a valid profile" );
- settingElement.detach();
+ final Optional valueAndMetaTuple = documentReader.readSetting( PwmSetting.APP_PROPERTY_OVERRIDES, null );
+ valueAndMetaTuple.ifPresent( ( t ) -> existingValues.addAll( ( List ) t.getValue().toNativeObject() ) );
}
+ existingValues.add( newValue );
+ rewriteAppPropertySettingElement( xmlDocument, existingValues );
}
}
+ element.detach();
+ }
+ }
+
+ private static void rewriteAppPropertySettingElement( final XmlDocument xmlDocument, final List newValues )
+ throws PwmUnrecoverableException
+ {
+ final StoredConfigurationFactory.XmlInputDocumentReader inputDocumentReader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument );
+
+ {
+ final Optional existingAppPropertySetting = inputDocumentReader.xpathForSetting( PwmSetting.APP_PROPERTY_OVERRIDES, null );
+ existingAppPropertySetting.ifPresent( XmlElement::detach );
}
+
+ final PwmSecurityKey pwmSecurityKey = inputDocumentReader.getKey();
+
+ final XmlElement settingElement = StoredConfigurationFactory.XmlOutputHandler.makeSettingXmlElement(
+ null,
+ PwmSetting.APP_PROPERTY_OVERRIDES,
+ null,
+ new StringArrayValue( newValues ),
+ XmlOutputProcessData.builder().storedValueEncoderMode( StoredValueEncoder.Mode.PLAIN ).pwmSecurityKey( pwmSecurityKey ).build() );
+ final Optional settingsElement = xmlDocument.getRootElement().getChild( StoredConfigXmlConstants.XML_ELEMENT_SETTING );
+ settingsElement.ifPresent( ( s ) -> s.addContent( settingElement ) );
}
}
- private void migrateAppProperties(
- )
- throws PwmUnrecoverableException
+ private static class UpdateDeprecatedAdComplexitySettings implements PwmExceptionLoggingConsumer
{
- final List appPropertiesElements = storedConfiguration.getXmlHelper().xpathForAppProperties();
- for ( final XmlElement element : appPropertiesElements )
+ @Override
+ public void accept( final StoredConfigurationModifier modifier )
+ throws PwmUnrecoverableException
{
- final List properties = element.getChildren();
- for ( final XmlElement property : properties )
+ final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
+ final Configuration configuration = new Configuration( oldConfig );
+ for ( final String profileID : configuration.getPasswordProfileIDs() )
{
- final String key = property.getAttributeValue( "key" );
- final String value = property.getText();
- if ( key != null && !key.isEmpty() && value != null && !value.isEmpty() )
+ if ( !oldConfig.isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) )
{
- LOGGER.info( () -> "migrating app-property config element '" + key + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey() );
- final String newValue = key + "=" + value;
- List existingValues = ( List ) storedConfiguration.readSetting( PwmSetting.APP_PROPERTY_OVERRIDES ).toNativeObject();
- if ( existingValues == null )
+ final boolean ad2003Enabled = ( boolean ) oldConfig.readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject();
+ final StoredValue value;
+ if ( ad2003Enabled )
+ {
+ value = new StringValue( ADPolicyComplexity.AD2003.toString() );
+ }
+ else
{
- existingValues = new ArrayList<>();
+ value = new StringValue( ADPolicyComplexity.NONE.toString() );
}
- existingValues = new ArrayList<>( existingValues );
- existingValues.add( newValue );
- storedConfiguration.writeSetting( PwmSetting.APP_PROPERTY_OVERRIDES, new StringArrayValue( existingValues ), null );
+ 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() );
+ final Optional valueMetaData = oldConfig.readMetaData(
+ StoredConfigItemKey.fromSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) );
+ final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
+ modifier.writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, userIdentity );
}
}
- element.detach();
}
}
- private void updateDeprecatedSettings( ) throws PwmUnrecoverableException
+ private static class UpdateDeprecatedMinPwdLifetimeSetting implements PwmExceptionLoggingConsumer
{
- final UserIdentity actor = new UserIdentity( "UpgradeProcessor", null );
- for ( final String profileID : storedConfiguration.profilesForSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY ) )
+ @Override
+ public void accept( final StoredConfigurationModifier modifier )
+ throws PwmUnrecoverableException
{
- if ( !storedConfiguration.isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) )
+ final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
+ for ( final String profileID : oldConfig.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) )
{
- final boolean ad2003Enabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject();
- final StoredValue value;
- if ( ad2003Enabled )
+ if ( !oldConfig.isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) )
{
- value = new StringValue( ADPolicyComplexity.AD2003.toString() );
- }
- else
- {
- value = new StringValue( ADPolicyComplexity.NONE.toString() );
+ final boolean enforceEnabled = ( boolean ) oldConfig.readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject();
+ final StoredValue value = enforceEnabled
+ ? new StringValue( RecoveryMinLifetimeOption.NONE.name() )
+ : new StringValue( RecoveryMinLifetimeOption.ALLOW.name() );
+ final ValueMetaData existingData = oldConfig.readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID );
+ final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null
+ ? existingData.getUserIdentity()
+ : null;
+ LOGGER.info( () -> "converting deprecated non-default setting "
+ + 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 )
+ + ", value=" + value.toNativeObject().toString() );
+ modifier.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor );
}
- LOGGER.warn( "converting deprecated non-default setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
- + " to replacement setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + value.toNativeObject().toString() );
- storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, actor );
- storedConfiguration.resetSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID, actor );
}
}
+ }
- for ( final String profileID : storedConfiguration.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) )
+ private static class UpdateDeprecatedPublicHealthSetting implements PwmExceptionLoggingConsumer
+ {
+ @Override
+ public void accept( final StoredConfigurationModifier modifier )
+ throws PwmUnrecoverableException
{
- if ( !storedConfiguration.isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) )
+ final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
+ if ( !oldConfig.isDefaultValue( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null ) )
{
- final boolean enforceEnabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject();
- final StoredValue value = enforceEnabled
- ? new StringValue( "NONE" )
- : new StringValue( "ALLOW" );
- final ValueMetaData existingData = storedConfiguration.readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID );
- LOGGER.warn( "converting deprecated non-default setting "
- + 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 )
- + ", value=" + value.toNativeObject().toString() );
- final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null
- ? existingData.getUserIdentity()
- : actor;
- storedConfiguration.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor );
- storedConfiguration.resetSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID, actor );
+ LOGGER.info( () -> "converting deprecated non-default setting "
+ + PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE )
+ + " to replacement setting " + PwmSetting.WEBSERVICES_PUBLIC_ENABLE.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) );
+ final Set existingValues = ( Set ) oldConfig.readSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null ).toNativeObject();
+ final Set newValues = new LinkedHashSet<>( existingValues );
+ newValues.add( WebServiceUsage.Health.name() );
+ newValues.add( WebServiceUsage.Statistics.name() );
+
+ final Optional valueMetaData = oldConfig.readMetaData(
+ StoredConfigItemKey.fromSetting( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null ) );
+ final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
+
+ modifier.writeSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null, new OptionListValue( newValues ), userIdentity );
}
}
+ }
- if ( !storedConfiguration.isDefaultValue( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES ) )
- {
- LOGGER.warn( "converting deprecated non-default setting "
- + PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE )
- + " to replacement setting " + PwmSetting.WEBSERVICES_PUBLIC_ENABLE.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) );
- final Set existingValues = (Set) storedConfiguration.readSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE ).toNativeObject();
- final Set newValues = new LinkedHashSet<>( existingValues );
- newValues.add( WebServiceUsage.Health.name() );
- newValues.add( WebServiceUsage.Statistics.name() );
- storedConfiguration.writeSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null, new OptionListValue( newValues ), actor );
- storedConfiguration.resetSetting( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null, actor );
- }
+ private static int readDocVersion( final XmlDocument xmlDocument )
+ {
+ final String xmlVersionStr = xmlDocument.getRootElement().getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_XML_VERSION );
+ return JavaHelper.silentParseInt( xmlVersionStr, 0 );
}
}
diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java b/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java
index 835a9f771..1c606de25 100644
--- a/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java
+++ b/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java
@@ -27,8 +27,8 @@ public enum ConfigurationProperty
LDAP_TEMPLATE( "configTemplate" ),
NOTES( "notes" ),
PASSWORD_HASH( "configPasswordHash" ),
- CONFIG_ON_START( "saveConfigOnStart" ),
- MODIFIFICATION_TIMESTAMP( "modificationTimestamp" ),
+ STORE_PLAINTEXT_VALUES( "storePlaintextValues" ),
+ MODIFICATION_TIMESTAMP( "modificationTimestamp" ),
IMPORT_LDAP_CERTIFICATES( "importLdapCertificates" ),;
private final String key;
diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java b/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
index 53ea43f78..fcea22432 100644
--- a/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
+++ b/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
@@ -25,13 +25,17 @@
import password.pwm.PwmApplicationMode;
import password.pwm.PwmConstants;
import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
import password.pwm.config.Configuration;
+import password.pwm.config.StoredValue;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmOperationalException;
import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.svc.event.AuditEvent;
+import password.pwm.svc.event.AuditRecordFactory;
import password.pwm.util.java.FileSystemUtility;
-import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.StringUtil;
import password.pwm.util.java.TimeDuration;
import password.pwm.util.logging.PwmLogger;
@@ -45,6 +49,8 @@
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
/**
* Read the PWM configuration.
@@ -58,9 +64,10 @@ public class ConfigurationReader
private final File configFile;
private final String configFileChecksum;
private Configuration configuration;
- private StoredConfigurationImpl storedConfiguration;
+ private StoredConfiguration storedConfiguration;
private ErrorInformation configFileError;
+
private PwmApplicationMode configMode = PwmApplicationMode.NEW;
private volatile boolean saveInProgress;
@@ -75,7 +82,7 @@ public ConfigurationReader( final File configFile ) throws PwmUnrecoverableExcep
this.storedConfiguration = readStoredConfig();
this.configFileError = null;
}
- catch ( PwmUnrecoverableException e )
+ catch ( final PwmUnrecoverableException e )
{
this.configFileError = e.getErrorInformation();
LOGGER.warn( "error reading configuration file: " + e.getMessage() );
@@ -83,7 +90,7 @@ public ConfigurationReader( final File configFile ) throws PwmUnrecoverableExcep
if ( storedConfiguration == null )
{
- this.storedConfiguration = StoredConfigurationImpl.newStoredConfiguration();
+ this.storedConfiguration = StoredConfigurationFactory.newConfig();
}
LOGGER.debug( () -> "configuration mode: " + configMode );
@@ -94,7 +101,7 @@ public PwmApplicationMode getConfigMode( )
return configMode;
}
- public StoredConfigurationImpl getStoredConfiguration( )
+ public StoredConfiguration getStoredConfiguration( )
{
return storedConfiguration;
}
@@ -103,19 +110,15 @@ public Configuration getConfiguration( ) throws PwmUnrecoverableException
{
if ( configuration == null )
{
- final StoredConfigurationImpl newStoredConfig = this.storedConfiguration == null
- ? StoredConfigurationImpl.newStoredConfiguration()
+ final StoredConfiguration newStoredConfig = this.storedConfiguration == null
+ ? StoredConfigurationFactory.newConfig()
: this.storedConfiguration;
configuration = new Configuration( newStoredConfig );
- if ( storedConfiguration != null )
- {
- storedConfiguration.lock();
- }
}
return configuration;
}
- private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableException
+ private StoredConfiguration readStoredConfig( ) throws PwmUnrecoverableException
{
LOGGER.debug( () -> "loading configuration file: " + configFile );
@@ -126,12 +129,34 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep
}
final Instant startTime = Instant.now();
+
+ /*
+ try
+ {
+ final InputStream theFileData = Files.newInputStream( configFile.toPath() );
+ final StoredConfiguration storedConfiguration = StoredConfigurationFactory.fromXml( theFileData );
+
+ System.out.println( TimeDuration.compactFromCurrent( startTime ) );
+
+
+ //final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final FileOutputStream fos = new FileOutputStream( new File( "/tmp/NEWCFG" ) );
+ StoredConfigurationFactory.toXml( storedConfiguration, fos );
+
+ //System.out.println( new String( baos.toByteArray(), "UTF-8" ) );
+ }
+ catch ( final Exception e )
+ {
+ e.printStackTrace( );
+ }
+ */
+
final InputStream theFileData;
try
{
theFileData = Files.newInputStream( configFile.toPath() );
}
- catch ( Exception e )
+ catch ( final Exception e )
{
final String errorMsg = "unable to read configuration file: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
@@ -143,13 +168,12 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep
throw new PwmUnrecoverableException( errorInformation );
}
- final StoredConfigurationImpl storedConfiguration;
+ final StoredConfiguration storedConfiguration;
try
{
- storedConfiguration = StoredConfigurationImpl.fromXml( theFileData );
- //restoredConfiguration = (new NGStoredConfigurationFactory()).fromXml(theFileData);
+ storedConfiguration = StoredConfigurationFactory.fromXml( theFileData );
}
- catch ( PwmUnrecoverableException e )
+ catch ( final PwmUnrecoverableException e )
{
final String errorMsg = "unable to parse configuration file: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
@@ -162,8 +186,8 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep
throw new PwmUnrecoverableException( errorInformation );
}
- final List validationErrorMsgs = storedConfiguration.validateValues();
- if ( validationErrorMsgs != null && !validationErrorMsgs.isEmpty() )
+ final List validationErrorMsgs = StoredConfigurationUtil.validateValues( storedConfiguration );
+ if ( !JavaHelper.isEmpty( validationErrorMsgs ) )
{
final String errorMsg = "value error in config file, please investigate: " + validationErrorMsgs.get( 0 );
final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
@@ -175,8 +199,8 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep
throw new PwmUnrecoverableException( errorInformation );
}
- final String configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE );
- if ( PwmConstants.TRIAL_MODE || ( configIsEditable != null && "true".equalsIgnoreCase( configIsEditable ) ) )
+ final Optional configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE );
+ if ( PwmConstants.TRIAL_MODE || ( configIsEditable.isPresent() && "true".equalsIgnoreCase( configIsEditable.get() ) ) )
{
this.configMode = PwmApplicationMode.CONFIGURATION;
}
@@ -189,11 +213,13 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep
final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime );
LOGGER.debug( () -> "configuration reading/parsing of " + fileSize + " complete in " + timeDuration.asLongString() );
+
+
return storedConfiguration;
}
public void saveConfiguration(
- final StoredConfigurationImpl storedConfiguration,
+ final StoredConfiguration storedConfiguration,
final PwmApplication pwmApplication,
final SessionLabel sessionLabel
)
@@ -216,18 +242,21 @@ public void saveConfiguration(
{
// increment the config epoch
- String epochStrValue = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_EPOCH );
+ String newEpochStrValue = "0";
try
{
- final BigInteger epochValue = epochStrValue == null || epochStrValue.length() < 0 ? BigInteger.ZERO : new BigInteger( epochStrValue );
- epochStrValue = epochValue.add( BigInteger.ONE ).toString();
+ final Optional storedEpochStrValue = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_EPOCH );
+ final BigInteger epochValue = storedEpochStrValue.map( BigInteger::new ).orElse( BigInteger.ZERO );
+ newEpochStrValue = epochValue.add( BigInteger.ONE ).toString();
}
- catch ( Exception e )
+ catch ( final Exception e )
{
LOGGER.error( sessionLabel, "error trying to parse previous config epoch property: " + e.getMessage() );
- epochStrValue = "0";
}
- storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_EPOCH, epochStrValue );
+
+ final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
+ modifier.writeConfigProperty( ConfigurationProperty.CONFIG_EPOCH, newEpochStrValue );
+ this.storedConfiguration = modifier.newStoredConfiguration();
}
if ( backupDirectory != null && !backupDirectory.exists() )
@@ -239,51 +268,97 @@ public void saveConfiguration(
}
}
- try
+ if ( pwmApplication != null && pwmApplication.getAuditManager() != null )
{
- final File tempWriteFile = new File( configFile.getAbsoluteFile() + ".new" );
- LOGGER.info( sessionLabel, () -> "beginning write to configuration file " + tempWriteFile );
- saveInProgress = true;
-
- try ( FileOutputStream fileOutputStream = new FileOutputStream( tempWriteFile, false ) )
- {
- storedConfiguration.toXml( fileOutputStream );
- }
+ auditModifiedSettings( pwmApplication, storedConfiguration, sessionLabel );
+ }
- LOGGER.info( () -> "saved configuration " + JsonUtil.serialize( storedConfiguration.toJsonDebugObject() ) );
- if ( pwmApplication != null )
- {
- final String actualChecksum = storedConfiguration.settingChecksum();
- pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum );
- }
+ try
+ {
+ outputConfigurationFile( storedConfiguration, pwmApplication, sessionLabel, backupRotations, backupDirectory );
+ }
+ finally
+ {
+ saveInProgress = false;
+ }
+ }
- LOGGER.trace( () -> "renaming file " + tempWriteFile.getAbsolutePath() + " to " + configFile.getAbsolutePath() );
- try
- {
- Files.move( tempWriteFile.toPath(), configFile.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE );
- }
- catch ( Exception e )
- {
- final String errorMsg = "unable to rename temporary save file from " + tempWriteFile.getAbsolutePath()
- + " to " + configFile.getAbsolutePath() + "; error: " + e.getMessage();
- throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
- }
+ private static void auditModifiedSettings( final PwmApplication pwmApplication, final StoredConfiguration newConfig, final SessionLabel sessionLabel )
+ throws PwmUnrecoverableException
+ {
+ final Set changedKeys = StoredConfigurationUtil.changedValues( newConfig, pwmApplication.getConfig().getStoredConfiguration() );
- if ( backupDirectory != null )
+ for ( final StoredConfigItemKey key : changedKeys )
+ {
+ if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING
+ || key.getRecordType() == StoredConfigItemKey.RecordType.LOCALE_BUNDLE )
{
- final String configFileName = configFile.getName();
- final String backupFilePath = backupDirectory.getAbsolutePath() + File.separatorChar + configFileName + "-backup";
- final File backupFile = new File( backupFilePath );
- FileSystemUtility.rotateBackups( backupFile, backupRotations );
- try ( FileOutputStream fileOutputStream = new FileOutputStream( backupFile, false ) )
+ final Optional storedValue = newConfig.readStoredValue( key );
+ if ( storedValue.isPresent() )
{
- storedConfiguration.toXml( fileOutputStream );
+ final Optional valueMetaData = newConfig.readMetaData( key );
+ final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
+ final String modifyMessage = "configuration record '" + key.getLabel( PwmConstants.DEFAULT_LOCALE )
+ + "' has been modified, new value: " + storedValue.get().toDebugString( PwmConstants.DEFAULT_LOCALE );
+ pwmApplication.getAuditManager().submit( new AuditRecordFactory( pwmApplication ).createUserAuditRecord(
+ AuditEvent.MODIFY_CONFIGURATION,
+ userIdentity,
+ sessionLabel,
+ modifyMessage
+ ) );
}
}
}
- finally
+ }
+
+ private void outputConfigurationFile(
+ final StoredConfiguration storedConfiguration,
+ final PwmApplication pwmApplication,
+ final SessionLabel sessionLabel,
+ final int backupRotations,
+ final File backupDirectory
+ )
+ throws IOException, PwmUnrecoverableException
+ {
+ final Instant saveFileStartTime = Instant.now();
+ final File tempWriteFile = new File( configFile.getAbsoluteFile() + ".new" );
+ LOGGER.info( sessionLabel, () -> "beginning write to configuration file " + tempWriteFile );
+ saveInProgress = true;
+
+ try ( FileOutputStream fileOutputStream = new FileOutputStream( tempWriteFile, false ) )
{
- saveInProgress = false;
+ StoredConfigurationFactory.toXml( storedConfiguration, fileOutputStream );
+ }
+
+ LOGGER.info( () -> "saved configuration in " + TimeDuration.compactFromCurrent( saveFileStartTime ) );
+ if ( pwmApplication != null )
+ {
+ final String actualChecksum = storedConfiguration.valueHash();
+ pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum );
+ }
+
+ LOGGER.trace( () -> "renaming file " + tempWriteFile.getAbsolutePath() + " to " + configFile.getAbsolutePath() );
+ try
+ {
+ Files.move( tempWriteFile.toPath(), configFile.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE );
+ }
+ catch ( final Exception e )
+ {
+ final String errorMsg = "unable to rename temporary save file from " + tempWriteFile.getAbsolutePath()
+ + " to " + configFile.getAbsolutePath() + "; error: " + e.getMessage();
+ throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
+ }
+
+ if ( backupDirectory != null )
+ {
+ final String configFileName = configFile.getName();
+ final String backupFilePath = backupDirectory.getAbsolutePath() + File.separatorChar + configFileName + "-backup";
+ final File backupFile = new File( backupFilePath );
+ FileSystemUtility.rotateBackups( backupFile, backupRotations );
+ try ( FileOutputStream fileOutputStream = new FileOutputStream( backupFile, false ) )
+ {
+ StoredConfigurationFactory.toXml( storedConfiguration, fileOutputStream );
+ }
}
}
diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigData.java b/server/src/main/java/password/pwm/config/stored/StoredConfigData.java
new file mode 100644
index 000000000..e75ac2b48
--- /dev/null
+++ b/server/src/main/java/password/pwm/config/stored/StoredConfigData.java
@@ -0,0 +1,70 @@
+/*
+ * 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 lombok.Builder;
+import lombok.Singular;
+import lombok.Value;
+import password.pwm.config.StoredValue;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Value
+@Builder( toBuilder = true )
+class StoredConfigData
+{
+ @Builder.Default
+ private String createTime = "";
+
+ @Builder.Default
+ private Instant modifyTime = Instant.now();
+
+ @Singular
+ private Map storedValues;
+
+ @Singular
+ private Map metaDatas;
+
+ @Value
+ static class ValueAndMetaCarrier
+ {
+ private final StoredConfigItemKey key;
+ private final StoredValue value;
+ private final ValueMetaData metaData;
+ }
+
+ static Map carrierAsMetaDataMap( final Collection input )
+ {
+ return input.stream()
+ .filter( ( t ) -> t.getKey() != null && t.getMetaData() != null )
+ .collect( Collectors.toMap( StoredConfigData.ValueAndMetaCarrier::getKey, StoredConfigData.ValueAndMetaCarrier::getMetaData ) );
+ }
+
+ static Map carrierAsStoredValueMap( final Collection input )
+ {
+ return input.stream()
+ .filter( ( t ) -> t.getKey() != null && t.getValue() != null )
+ .collect( Collectors.toMap( StoredConfigData.ValueAndMetaCarrier::getKey, StoredConfigData.ValueAndMetaCarrier::getValue ) );
+ }
+}
diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java b/server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java
new file mode 100644
index 000000000..7aa379ad1
--- /dev/null
+++ b/server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java
@@ -0,0 +1,235 @@
+/*
+ * 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.PwmConstants;
+import password.pwm.config.PwmSetting;
+import password.pwm.i18n.Config;
+import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
+
+import java.io.Serializable;
+import java.util.Locale;
+import java.util.Objects;
+
+public class StoredConfigItemKey implements Serializable, Comparable
+{
+ public enum RecordType
+ {
+ SETTING( "Setting" ),
+ LOCALE_BUNDLE ( "Localization" ),
+ PROPERTY ( "Property" ),;
+
+ private final String label;
+
+ RecordType( final String label )
+ {
+ this.label = label;
+ }
+
+ public String getLabel()
+ {
+ return label;
+ }
+ }
+
+ private final RecordType recordType;
+ private final String recordID;
+ private 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.recordID = recordID;
+ this.profileID = profileID;
+ }
+
+ public RecordType getRecordType()
+ {
+ return recordType;
+ }
+
+ public String getRecordID()
+ {
+ return recordID;
+ }
+
+ public String getProfileID()
+ {
+ return profileID;
+ }
+
+ static StoredConfigItemKey fromSetting( final PwmSetting pwmSetting, final String profileID )
+ {
+ return new StoredConfigItemKey( RecordType.SETTING, pwmSetting.getKey(), profileID );
+ }
+
+ static StoredConfigItemKey fromLocaleBundle( final PwmLocaleBundle localeBundle, final String key )
+ {
+ return new StoredConfigItemKey( RecordType.LOCALE_BUNDLE, localeBundle.getKey(), key );
+ }
+
+ static StoredConfigItemKey fromConfigurationProperty( final ConfigurationProperty configurationProperty )
+ {
+ return new StoredConfigItemKey( RecordType.PROPERTY, configurationProperty.getKey(), null );
+ }
+
+ public boolean isValid()
+ {
+ try
+ {
+ validate();
+ return true;
+ }
+ catch ( final IllegalStateException e )
+ {
+ /* ignore */
+ }
+ return false;
+ }
+
+ public void validate()
+ {
+ switch ( recordType )
+ {
+ case SETTING:
+ {
+ final PwmSetting pwmSetting = this.toPwmSetting();
+ final boolean hasProfileID = !StringUtil.isEmpty( profileID );
+ if ( pwmSetting.getCategory().hasProfiles() && !hasProfileID )
+ {
+ throw new IllegalStateException( "profileID is required for setting " + pwmSetting.getKey() );
+ }
+ else if ( !pwmSetting.getCategory().hasProfiles() && hasProfileID )
+ {
+ throw new IllegalStateException( "profileID is not required for setting " + pwmSetting.getKey() );
+ }
+ }
+ break;
+
+ case LOCALE_BUNDLE:
+ {
+ Objects.requireNonNull( profileID, "profileID is required when recordType is LOCALE_BUNDLE" );
+ final PwmLocaleBundle pwmLocaleBundle = toLocaleBundle();
+ if ( !pwmLocaleBundle.getDisplayKeys().contains( profileID ) )
+ {
+ throw new IllegalStateException( "key '" + profileID + "' is unrecognized for locale bundle " + pwmLocaleBundle.name() );
+ }
+ }
+ break;
+
+ case PROPERTY:
+ break;
+
+ default:
+ JavaHelper.unhandledSwitchStatement( recordType );
+ }
+ }
+
+ public String getLabel( final Locale locale )
+ {
+ final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
+
+ switch ( recordType )
+ {
+ case SETTING:
+ return recordType.getLabel() + separator + toPwmSetting().toMenuLocationDebug( profileID, locale );
+
+ case PROPERTY:
+ return recordType.getLabel() + separator + this.getRecordID();
+
+ case LOCALE_BUNDLE:
+ return recordType.getLabel()
+ + separator
+ + this.getRecordID()
+ + separator
+ + this.getProfileID();
+
+ default:
+ JavaHelper.unhandledSwitchStatement( recordType );
+ }
+
+ throw new IllegalStateException( );
+ }
+
+ public PwmSetting toPwmSetting()
+ {
+ if ( getRecordType() != RecordType.SETTING )
+ {
+ throw new IllegalStateException( "attempt to read pwmSetting key for non-setting ConfigItemKey" );
+ }
+
+ return PwmSetting.forKey( this.recordID );
+ }
+
+ public PwmLocaleBundle toLocaleBundle()
+ {
+ if ( getRecordType() != RecordType.LOCALE_BUNDLE )
+ {
+ throw new IllegalStateException( "attempt to read PwmLocaleBundle key for non-locale ConfigItemKey" );
+ }
+
+ return PwmLocaleBundle.forKey( this.recordID )
+ .orElseThrow( () -> new IllegalStateException( "unexpected key value for locale bundle: " + this.recordID ) );
+ }
+
+ public ConfigurationProperty toConfigurationProperty()
+ {
+ if ( getRecordType() != RecordType.PROPERTY )
+ {
+ throw new IllegalStateException( "attempt to read ConfigurationProperty key for non-config property ConfigItemKey" );
+ }
+
+ 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
+ public int compareTo( final Object o )
+ {
+ return getLabel( PwmConstants.DEFAULT_LOCALE ).compareTo( o.toString() );
+ }
+}
diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigReferenceBean.java b/server/src/main/java/password/pwm/config/stored/StoredConfigReferenceBean.java
deleted file mode 100644
index 721f9b364..000000000
--- a/server/src/main/java/password/pwm/config/stored/StoredConfigReferenceBean.java
+++ /dev/null
@@ -1,67 +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 lombok.Value;
-
-import java.io.Serializable;
-
-@Value
-public class StoredConfigReferenceBean implements StoredConfigReference, Serializable, Comparable
-{
- private RecordType recordType;
- private String recordID;
- private String profileID;
-
- public StoredConfigReferenceBean( final RecordType type, final String recordID, final String profileID )
- {
- if ( type == null )
- {
- throw new NullPointerException( "recordType can not be null" );
- }
-
- if ( recordID == null )
- {
- throw new NullPointerException( "recordID can not be null" );
- }
-
- this.recordType = type;
- this.recordID = recordID;
- this.profileID = profileID;
- }
-
-
- @Override
- public String toString( )
- {
- return this.getRecordType().toString()
- + "-"
- + ( this.getProfileID() == null ? "" : this.getProfileID() )
- + "-"
- + this.getRecordID();
- }
-
- @Override
- public int compareTo( final Object o )
- {
- return toString().compareTo( o.toString() );
- }
-}
diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigXmlConstants.java b/server/src/main/java/password/pwm/config/stored/StoredConfigXmlConstants.java
new file mode 100644
index 000000000..20550b641
--- /dev/null
+++ b/server/src/main/java/password/pwm/config/stored/StoredConfigXmlConstants.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+public class StoredConfigXmlConstants
+{
+ public static final String XML_ATTRIBUTE_TYPE = "type";
+ public static final String XML_ELEMENT_ROOT = "PwmConfiguration";
+ public static final String XML_ELEMENT_PROPERTIES = "properties";
+ public static final String XML_ELEMENT_PROPERTY = "property";
+ public static final String XML_ELEMENT_SETTINGS = "settings";
+ public static final String XML_ELEMENT_SETTING = "setting";
+ public static final String XML_ELEMENT_DEFAULT = "default";
+ public static final String XML_ELEMENT_LOCALEBUNDLE = "localeBundle";
+ public static final String XML_ELEMENT_LABEL = "label";
+ public static final String XML_ELEMENT_VALUE = "value";
+
+ public static final String XML_ATTRIBUTE_KEY = "key";
+ public static final String XML_ATTRIBUTE_SYNTAX = "syntax";
+ public static final String XML_ATTRIBUTE_PROFILE = "profile";
+ public static final String XML_ATTRIBUTE_VALUE_APP = "app";
+ public static final String XML_ATTRIBUTE_VALUE_CONFIG = "config";
+ public static final String XML_ATTRIBUTE_CREATE_TIME = "createTime";
+ public static final String XML_ATTRIBUTE_MODIFY_TIME = "modifyTime";
+ public static final String XML_ATTRIBUTE_MODIFY_USER = "modifyUser";
+ public static final String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion";
+ public static final String XML_ATTRIBUTE_BUNDLE = "bundle";
+ public static final String XML_ATTRIBUTE_XML_VERSION = "xmlVersion";
+ public static final String XML_ATTRRIBUTE_PWM_BUILD = "pwmBuild";
+ public static final String XML_ATTRIBUTE_PWM_VERSION = "pwmVersion";
+ public static final String XML_ATTRIBUTE_LOCALE = "locale";
+}
diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java b/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java
index d0d908f17..dc7073c13 100644
--- a/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java
+++ b/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java
@@ -20,81 +20,48 @@
package password.pwm.config.stored;
-import password.pwm.bean.UserIdentity;
import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.PwmSettingTemplateSet;
import password.pwm.config.StoredValue;
import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.i18n.PwmLocaleBundle;
import password.pwm.util.secure.PwmSecurityKey;
import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
public interface StoredConfiguration
{
- String XML_ELEMENT_ROOT = "PwmConfiguration";
- String XML_ELEMENT_PROPERTIES = "properties";
- String XML_ELEMENT_PROPERTY = "property";
- String XML_ELEMENT_SETTINGS = "settings";
- String XML_ELEMENT_SETTING = "setting";
- String XML_ELEMENT_DEFAULT = "default";
- String XML_ELEMENT_LOCALEBUNDLE = "localeBundle";
-
- String XML_ATTRIBUTE_TYPE = "type";
- String XML_ATTRIBUTE_KEY = "key";
- String XML_ATTRIBUTE_SYNTAX = "syntax";
- String XML_ATTRIBUTE_PROFILE = "profile";
- String XML_ATTRIBUTE_VALUE_APP = "app";
- String XML_ATTRIBUTE_VALUE_CONFIG = "config";
- String XML_ATTRIBUTE_CREATE_TIME = "createTime";
- String XML_ATTRIBUTE_MODIFY_TIME = "modifyTime";
- String XML_ATTRIBUTE_MODIFY_USER = "modifyUser";
- String XML_ATTRIBUTE_MODIFY_USER_PROFILE = "modifyUserProfile";
- String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion";
- String XML_ATTRIBUTE_BUNDLE = "bundle";
-
-
PwmSecurityKey getKey( ) throws PwmUnrecoverableException;
- Instant modifyTime( );
+ String createTime();
- boolean isLocked( );
+ Instant modifyTime( );
- String readConfigProperty( ConfigurationProperty propertyName );
+ Optional readConfigProperty( ConfigurationProperty propertyName );
- void writeConfigProperty(
- ConfigurationProperty propertyName,
- String value
- );
+ PwmSettingTemplateSet getTemplateSet();
- void lock( );
+ List profilesForSetting( PwmSetting pwmSetting );
ValueMetaData readSettingMetadata( PwmSetting setting, String profileID );
- void resetSetting( PwmSetting setting, String profileID, UserIdentity userIdentity );
+ Map readLocaleBundleMap( PwmLocaleBundle bundleName, String keyName );
- boolean isDefaultValue( PwmSetting setting );
+ StoredValue readSetting( PwmSetting setting, String profileID );
boolean isDefaultValue( PwmSetting setting, String profileID );
- StoredValue readSetting( PwmSetting setting );
-
- StoredValue readSetting( PwmSetting setting, String profileID );
-
- void copyProfileID( PwmSettingCategory category, String sourceID, String destinationID, UserIdentity userIdentity )
- throws PwmUnrecoverableException;
+ String valueHash();
- void writeSetting(
- PwmSetting setting,
- StoredValue value,
- UserIdentity userIdentity
- ) throws PwmUnrecoverableException;
+ Set modifiedItems();
- void writeSetting(
- PwmSetting setting,
- String profileID,
- StoredValue value,
- UserIdentity userIdentity
- ) throws PwmUnrecoverableException;
+ Optional readMetaData( StoredConfigItemKey storedConfigItemKey );
+ Optional readStoredValue( StoredConfigItemKey storedConfigItemKey );
+ StoredConfiguration copy();
}
diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java b/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java
index c6979543a..8dc1af38f 100644
--- a/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java
+++ b/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java
@@ -20,14 +20,682 @@
package password.pwm.config.stored;
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingFlag;
+import password.pwm.config.PwmSettingSyntax;
+import password.pwm.config.PwmSettingTemplate;
+import password.pwm.config.PwmSettingTemplateSet;
+import password.pwm.config.StoredValue;
+import password.pwm.config.value.LocalizedStringValue;
+import password.pwm.config.value.StoredValueEncoder;
+import password.pwm.config.value.StringValue;
+import password.pwm.config.value.ValueFactory;
+import password.pwm.error.PwmException;
import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.LazySupplier;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.XmlDocument;
+import password.pwm.util.java.XmlElement;
+import password.pwm.util.java.XmlFactory;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroMachine;
+import password.pwm.util.secure.PwmSecurityKey;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.Set;
-interface StoredConfigurationFactory
+public class StoredConfigurationFactory
{
- StoredConfiguration fromXml( InputStream inputStream ) throws PwmUnrecoverableException;
+ private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationFactory.class );
- void toXml( OutputStream outputStream );
+ private static final String XML_FORMAT_VERSION = "5";
+
+
+ public static StoredConfiguration newConfig() 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.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 )
+ throws PwmUnrecoverableException
+ {
+ final XmlFactory xmlFactory = XmlFactory.getFactory();
+ final XmlDocument xmlDocument = xmlFactory.parseXml( inputStream );
+ ConfigurationCleaner.preProcessXml( xmlDocument );
+
+ final XmlInputDocumentReader xmlInputDocumentReader = new XmlInputDocumentReader( xmlDocument );
+ final StoredConfigData storedConfigData = xmlInputDocumentReader.getStoredConfigData();
+ final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( new StoredConfigurationImpl( storedConfigData ) );
+ ConfigurationCleaner.postProcessStoredConfig( modifier );
+
+ return modifier.newStoredConfiguration();
+ }
+
+ public static void toXml(
+ final StoredConfiguration storedConfiguration,
+ final OutputStream outputStream
+ )
+ throws PwmUnrecoverableException, IOException
+ {
+ toXml( storedConfiguration, outputStream, OutputSettings.builder().build() );
+ }
+
+ public static void toXml(
+ final StoredConfiguration storedConfiguration,
+ final OutputStream outputStream,
+ final OutputSettings outputSettings
+ )
+ throws PwmUnrecoverableException, IOException
+ {
+ final XmlFactory xmlFactory = XmlFactory.getFactory();
+ final XmlDocument xmlDocument = xmlFactory.newDocument( StoredConfigXmlConstants.XML_ELEMENT_ROOT );
+
+ XmlOutputHandler.makeXmlOutput( storedConfiguration, xmlDocument.getRootElement(), outputSettings );
+
+ xmlFactory.outputDocument( xmlDocument, outputStream );
+ }
+
+ static class XmlInputDocumentReader
+ {
+ private final XmlDocument document;
+
+ XmlInputDocumentReader( final XmlDocument document )
+ {
+ this.document = document;
+ }
+
+ StoredConfigData getStoredConfigData()
+ {
+ final String createTime = readCreateTime();
+ final Instant modifyTime = readModifyTime();
+
+ final List values = new ArrayList<>();
+ values.addAll( readProperties() );
+ values.addAll( readSettings() );
+ values.addAll( readLocaleBundles() );
+ return StoredConfigData.builder()
+ .createTime( createTime )
+ .modifyTime( modifyTime )
+ .metaDatas( StoredConfigData.carrierAsMetaDataMap( values ) )
+ .storedValues( StoredConfigData.carrierAsStoredValueMap( values ) )
+ .build();
+ }
+
+ private Collection readProperties()
+ {
+ final List valueAndMetaWrapper = new ArrayList<>();
+ for ( final ConfigurationProperty configurationProperty : ConfigurationProperty.values() )
+ {
+ final Optional propertyElement = xpathForConfigProperty( configurationProperty );
+ if ( propertyElement.isPresent() && !StringUtil.isEmpty( propertyElement.get().getText() ) )
+ {
+ final StoredConfigItemKey key = StoredConfigItemKey.fromConfigurationProperty( configurationProperty );
+ final StoredValue storedValue = new StringValue( propertyElement.get().getText() );
+ final ValueMetaData metaData = readMetaDataFromXmlElement( key, propertyElement.get() ).orElse( null );
+ valueAndMetaWrapper.add( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) );
+ }
+ }
+ return valueAndMetaWrapper;
+ }
+
+ private Collection readSettings()
+ {
+ final List returnList = new ArrayList<>();
+ for ( final PwmSetting pwmSetting : PwmSetting.values() )
+ {
+ if ( !pwmSetting.getCategory().hasProfiles() )
+ {
+ readSetting( pwmSetting, null ).ifPresent( returnList::add );
+ }
+ }
+
+ for ( final PwmSetting pwmSetting : PwmSetting.values() )
+ {
+ if ( pwmSetting.getCategory().hasProfiles() )
+ {
+ final List profileIDs = profilesForSetting( pwmSetting );
+ for ( final String profileID : profileIDs )
+ {
+ readSetting( pwmSetting, profileID ).ifPresent( returnList::add );
+ }
+ }
+ }
+ return returnList;
+ }
+
+ Optional readSetting( final PwmSetting setting, final String profileID )
+ {
+ final Optional settingElement = xpathForSetting( setting, profileID );
+
+ if ( !settingElement.isPresent() )
+ {
+ return Optional.empty();
+ }
+
+ if ( settingElement.get().getChild( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT ).isPresent() )
+ {
+ return Optional.empty();
+ }
+
+ try
+ {
+ final StoredValue storedValue = ValueFactory.fromXmlValues( setting, settingElement.get(), getKey() );
+ final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
+ final ValueMetaData metaData = readMetaDataFromXmlElement( key, settingElement.get() ).orElse( null );
+ return Optional.of( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) );
+ }
+ catch ( final PwmException e )
+ {
+ final String errorMsg = "unexpected error reading setting '" + setting.getKey() + "' profile '" + profileID + "', error: " + e.getMessage();
+ throw new IllegalStateException( errorMsg );
+ }
+ }
+
+ List profilesForSetting( final PwmSetting pwmSetting )
+ {
+ if ( !pwmSetting.getCategory().hasProfiles() && pwmSetting.getSyntax() != PwmSettingSyntax.PROFILE )
+ {
+ return Collections.emptyList();
+ }
+
+ final PwmSetting profileSetting;
+ if ( pwmSetting.getSyntax() == PwmSettingSyntax.PROFILE )
+ {
+ profileSetting = pwmSetting;
+ }
+ else
+ {
+ profileSetting = pwmSetting.getCategory().getProfileSetting();
+ }
+
+ final StoredValue effectiveValue;
+ {
+ final Optional configuredValue = readSetting( profileSetting, null );
+ if ( configuredValue.isPresent() )
+ {
+ effectiveValue = configuredValue.get().getValue();
+ }
+ else
+ {
+ effectiveValue = profileSetting.getDefaultValue( templateSetSupplier.get() );
+ }
+ }
+
+ final List settingValues = ( List ) effectiveValue.toNativeObject();
+ final LinkedList profiles = new LinkedList<>( settingValues );
+ profiles.removeIf( StringUtil::isEmpty );
+ return Collections.unmodifiableList( profiles );
+ }
+
+
+ public PwmSecurityKey getKey() throws PwmUnrecoverableException
+ {
+ final XmlElement rootElement = document.getRootElement();
+ final String createTimeString = rootElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_CREATE_TIME );
+ return new PwmSecurityKey( createTimeString + "StoredConfiguration" );
+ }
+
+ String readCreateTime()
+ {
+ final XmlElement rootElement = document.getRootElement();
+ final String createTimeString = rootElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_CREATE_TIME );
+ if ( StringUtil.isEmpty( createTimeString ) )
+ {
+ throw new IllegalStateException( "missing createTime timestamp" );
+ }
+ else
+ {
+ return createTimeString;
+ }
+ }
+
+ Instant readModifyTime()
+ {
+ final XmlElement rootElement = document.getRootElement();
+ final String modifyTimeString = rootElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_TIME );
+ if ( !StringUtil.isEmpty( modifyTimeString ) )
+ {
+ try
+ {
+ return JavaHelper.parseIsoToInstant( modifyTimeString );
+ }
+ catch ( final Exception e )
+ {
+ LOGGER.error( "error parsing root last modified timestamp: " + e.getMessage() );
+ }
+ }
+
+ return null;
+ }
+
+ private final LazySupplier