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