Browse Source

pwmsetting performance improvements

Jason Rivard 3 years ago
parent
commit
a9621bca98
21 changed files with 975 additions and 734 deletions
  1. 1 0
      build/checkstyle.xml
  2. 1 1
      onejar/src/main/java/password/pwm/onejar/ArgumentParser.java
  3. 0 3
      server/src/main/java/password/pwm/PwmApplication.java
  4. 101 50
      server/src/main/java/password/pwm/config/PwmSetting.java
  5. 1 1
      server/src/main/java/password/pwm/config/PwmSettingFlag.java
  6. 244 0
      server/src/main/java/password/pwm/config/PwmSettingMetaData.java
  7. 0 430
      server/src/main/java/password/pwm/config/PwmSettingMetaDataReader.java
  8. 1 1
      server/src/main/java/password/pwm/config/PwmSettingTemplate.java
  9. 4 13
      server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java
  10. 6 18
      server/src/main/java/password/pwm/config/PwmSettingXml.java
  11. 70 0
      server/src/main/java/password/pwm/config/TemplateSetReference.java
  12. 1 1
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  13. 4 1
      server/src/main/java/password/pwm/http/PwmSession.java
  14. 330 153
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  15. 136 26
      server/src/main/resources/password/pwm/config/PwmSetting.xsd
  16. 19 0
      server/src/test/java/password/pwm/config/PwmSettingCategoryTest.java
  17. 0 33
      server/src/test/java/password/pwm/config/PwmSettingMetaDataReaderTest.java
  18. 29 1
      server/src/test/java/password/pwm/config/PwmSettingTest.java
  19. 25 0
      server/src/test/java/password/pwm/config/PwmSettingXmlTest.java
  20. 1 1
      webapp/src/main/webapp/public/resources/js/configeditor-settings-stringarray.js
  21. 1 1
      webapp/src/main/webapp/public/resources/js/configeditor-settings.js

+ 1 - 0
build/checkstyle.xml

@@ -162,6 +162,7 @@
         <module name="UnusedImports"/>
         <module name="ImportControl">
             <property name="file" value="${basedir}/build/checkstyle-import.xml"/>
+            <property name="path" value="^.*[\\/]src[\\/]main[\\/].*$"/>
         </module>
 
 

+ 1 - 1
onejar/src/main/java/password/pwm/onejar/ArgumentParser.java

@@ -114,7 +114,7 @@ public class ArgumentParser
     private Map<Argument, String> mapFromProperties( final String filename ) throws ArgumentParserException
     {
         final Properties props = new Properties();
-        try ( InputStream is = new FileInputStream( new File( filename ) ) )
+        try ( InputStream is = new FileInputStream( filename ) )
         {
             props.load( is );
         }

+ 0 - 3
server/src/main/java/password/pwm/PwmApplication.java

@@ -25,7 +25,6 @@ import password.pwm.bean.DomainID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingMetaDataReader;
 import password.pwm.config.PwmSettingScope;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -291,8 +290,6 @@ public class PwmApplication
 
         UserAgentUtils.initializeCache();
 
-        PwmSettingMetaDataReader.initCache();
-
         LOGGER.trace( sessionLabel, () -> "completed post init tasks", TimeDuration.fromCurrent( startTime ) );
     }
 

+ 101 - 50
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -20,21 +20,30 @@
 
 package password.pwm.config;
 
-import lombok.Value;
+import org.jrivard.xmlchai.XmlElement;
 import password.pwm.PwmConstants;
+import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.StoredValue;
-import password.pwm.util.java.CollectionUtil;
+import password.pwm.config.value.ValueFactory;
+import password.pwm.i18n.Config;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.macro.MacroRequest;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -1305,7 +1314,6 @@ public enum PwmSetting
     HELPDESK_ENABLE_OTP_VERIFY(
             "helpdesk.otp.verify", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_BASE ),;
 
-
     private static final Map<String, PwmSetting> KEY_MAP = Arrays.stream( values() )
             .collect( Collectors.toUnmodifiableMap( PwmSetting::getKey, Function.identity() ) );
 
@@ -1320,7 +1328,19 @@ public enum PwmSetting
     private final String key;
     private final PwmSettingSyntax syntax;
     private final PwmSettingCategory category;
-    private final PwmSettingMetaDataReader pwmSettingMetaDataReader;
+
+    private static final Map<PwmSetting, List<TemplateSetReference<StoredValue>>> DEFAULT_VALUE_CACHE = initDefaultValueCache();
+
+    private final transient Supplier<String> defaultMenuLocation = new LazySupplier<>(
+            () -> readMenuLocationDebug( this, null, PwmConstants.DEFAULT_LOCALE ) );
+
+    private final transient Supplier<String> defaultLocaleLabel = new LazySupplier<>(
+            () -> readLabel( this, PwmConstants.DEFAULT_LOCALE ) );
+
+    private final transient Supplier<String> defaultLocaleDescription = new LazySupplier<>(
+            () -> readDescription( this, PwmConstants.DEFAULT_LOCALE ) );
+
+
 
     PwmSetting(
             final String key,
@@ -1331,7 +1351,6 @@ public enum PwmSetting
         this.key = key;
         this.syntax = syntax;
         this.category = category;
-        this.pwmSettingMetaDataReader = new PwmSettingMetaDataReader( this );
     }
 
     public static Comparator<PwmSetting> menuLocationComparator()
@@ -1359,9 +1378,14 @@ public enum PwmSetting
         return syntax;
     }
 
+    private PwmSettingMetaData getPwmSettingMetaData()
+    {
+        return PwmSettingMetaData.forSetting( this );
+    }
+
     private List<TemplateSetReference<StoredValue>> getDefaultValue()
     {
-        return pwmSettingMetaDataReader.getDefaultValue();
+        return DEFAULT_VALUE_CACHE.get( this );
     }
 
     public StoredValue getDefaultValue( final PwmSettingTemplateSet templateSet )
@@ -1383,52 +1407,62 @@ public enum PwmSetting
 
     public Map<PwmSettingProperty, String> getProperties( )
     {
-        return pwmSettingMetaDataReader.getProperties();
+        return getPwmSettingMetaData().getProperties();
     }
 
     public Set<PwmSettingFlag> getFlags( )
     {
-        return pwmSettingMetaDataReader.getFlags();
+        return getPwmSettingMetaData().getFlags();
     }
 
     public Map<String, String> getOptions()
     {
-        return pwmSettingMetaDataReader.getOptions();
+        return getPwmSettingMetaData().getOptions();
     }
 
     public String getLabel( final Locale locale )
     {
-        return pwmSettingMetaDataReader.getLabel( locale );
+        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) )
+        {
+            return defaultLocaleLabel.get();
+        }
+
+        return readLabel( this, locale );
     }
 
     public String getDescription( final Locale locale )
     {
-        return pwmSettingMetaDataReader.getDescription( locale );
+        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) )
+        {
+            return defaultLocaleDescription.get();
+        }
+
+        return readDescription( this, locale );
     }
 
     public String getExample( final PwmSettingTemplateSet template )
     {
-        return pwmSettingMetaDataReader.getExample( template );
+        return TemplateSetReference.referenceForTempleSet( getPwmSettingMetaData().getExamples(), template );
     }
 
     public boolean isRequired( )
     {
-        return pwmSettingMetaDataReader.isRequired();
+        return getPwmSettingMetaData().isRequired();
     }
 
     public boolean isHidden( )
     {
-        return pwmSettingMetaDataReader.isHidden();
+        return getPwmSettingMetaData().isHidden();
     }
 
     public int getLevel( )
     {
-        return pwmSettingMetaDataReader.getLevel();
+        return getPwmSettingMetaData().getLevel();
     }
 
     public Pattern getRegExPattern( )
     {
-        return pwmSettingMetaDataReader.getRegExPattern();
+        return getPwmSettingMetaData().getPattern();
     }
 
     public static Optional<PwmSetting> forKey( final String key )
@@ -1441,12 +1475,19 @@ public enum PwmSetting
             final Locale locale
     )
     {
-        return pwmSettingMetaDataReader.toMenuLocationDebug( profileID, locale );
+        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) && StringUtil.isEmpty( profileID ) )
+        {
+            return defaultMenuLocation.get();
+        }
+        else
+        {
+            return readMenuLocationDebug( this, profileID, locale );
+        }
     }
 
     public Collection<LDAPPermissionInfo> getLDAPPermissionInfo()
     {
-        return pwmSettingMetaDataReader.getLDAPPermissionInfo();
+        return getPwmSettingMetaData().getLdapPermissionInfo();
     }
 
     public static List<PwmSetting> sortedValues()
@@ -1454,46 +1495,56 @@ public enum PwmSetting
         return SORTED_VALUES;
     }
 
-    @Value
-    static class TemplateSetReference<T>
+    private static Map<PwmSetting, List<TemplateSetReference<StoredValue>>> initDefaultValueCache()
     {
-        private final T reference;
-        private final Set<PwmSettingTemplate> settingTemplates;
-
-        static <T> T referenceForTempleSet(
-                final List<TemplateSetReference<T>> templateSetReferences,
-                final PwmSettingTemplateSet pwmSettingTemplateSet
-        )
+        final Map<PwmSetting, List<TemplateSetReference<StoredValue>>> map = new EnumMap<>( PwmSetting.class );
+        for ( final XmlElement settingElement : PwmSettingXml.readAllSettingXmlElements() )
         {
-            final PwmSettingTemplateSet effectiveTemplateSet = pwmSettingTemplateSet == null
-                    ? PwmSettingTemplateSet.getDefault()
-                    : pwmSettingTemplateSet;
-
-            if ( templateSetReferences == null || templateSetReferences.isEmpty() )
+            final List<TemplateSetReference<StoredValue>> returnObj = new ArrayList<>();
+            final PwmSetting pwmSetting = PwmSetting.forKey( settingElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_KEY ).orElseThrow() ).orElseThrow();
+            final List<XmlElement> defaultElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_DEFAULT );
+            if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
             {
-                throw new IllegalStateException( "templateSetReferences can not be null" );
+                returnObj.add( new TemplateSetReference<>( new PasswordValue( null ), Collections.emptySet() ) );
             }
-
-            if ( templateSetReferences.size() == 1 )
-            {
-                return templateSetReferences.get( 0 ).getReference();
-            }
-
-            for ( int matchCountExamSize = templateSetReferences.size(); matchCountExamSize > 0; matchCountExamSize-- )
+            else
             {
-                for ( final TemplateSetReference<T> templateSetReference : templateSetReferences )
+                for ( final XmlElement defaultElement : defaultElements )
                 {
-                    final Set<PwmSettingTemplate> temporarySet = CollectionUtil.copyToEnumSet( templateSetReference.getSettingTemplates(), PwmSettingTemplate.class );
-                    temporarySet.retainAll( effectiveTemplateSet.getTemplates() );
-                    final int matchCount = temporarySet.size();
-                    if ( matchCount == matchCountExamSize )
-                    {
-                        return templateSetReference.getReference();
-                    }
+                    final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement );
+                    final StoredValue storedValue = ValueFactory.fromXmlValues( pwmSetting, defaultElement, null );
+                    returnObj.add( new TemplateSetReference<>( storedValue, definedTemplates ) );
                 }
             }
-
-            return templateSetReferences.get( 0 ).getReference();
+            if ( returnObj.isEmpty() )
+            {
+                throw new IllegalStateException( "no default value for setting " + pwmSetting.getKey() );
+            }
+            map.put( pwmSetting, returnObj );
         }
+        return map;
+    }
+
+    private static String readLabel( final PwmSetting pwmSetting, final Locale locale )
+    {
+        return readStringProperty( password.pwm.i18n.PwmSetting.SETTING_LABEL_PREFIX + pwmSetting.getKey(), locale );
+    }
+
+    private static String readDescription( final PwmSetting pwmSetting, final Locale locale )
+    {
+        return readStringProperty( password.pwm.i18n.PwmSetting.SETTING_DESCRIPTION_PREFIX + pwmSetting.getKey(), locale );
+    }
+
+    private static String readStringProperty( final String key, final Locale locale )
+    {
+        final String storedText = LocaleHelper.getLocalizedMessage( locale, key, null, password.pwm.i18n.PwmSetting.class );
+        final MacroRequest macroRequest = MacroRequest.forStatic();
+        return macroRequest.expandMacros( storedText );
+    }
+
+    private static String readMenuLocationDebug( final PwmSetting pwmSetting, final String profileID, final Locale locale )
+    {
+        final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
+        return pwmSetting.getCategory().toMenuLocationDebug( profileID, locale ) + separator + pwmSetting.getLabel( locale );
     }
 }

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

@@ -29,7 +29,7 @@ public enum PwmSettingFlag
     MacroSupport,
 
     /* Setting uses LDAP DN syntax */
-    ldapDNsyntax,
+    ldapDnSyntax,
 
     /* Setting must be a valid email address format */
     emailSyntax,

+ 244 - 0
server/src/main/java/password/pwm/config/PwmSettingMetaData.java

@@ -0,0 +1,244 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 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;
+
+import lombok.Builder;
+import lombok.Value;
+import org.jrivard.xmlchai.XmlElement;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.macro.MacroRequest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+@Value
+@Builder
+/**
+ * Utility class for reading PwmSetting.xml values and making them available to PWM.  Since PwmSetting
+ * enums are fairly complex, there can be issues with static initialization circular dependencies during
+ * initialization of the classes in this package.
+ *
+ * This class tries to be self-sufficient during initialization, and then provide values for {@link PwmSetting}
+ * method invocations.
+ */
+class PwmSettingMetaData
+{
+    private static final Map<PwmSetting, PwmSettingMetaData> META_DATA_MAP = initMap();
+
+    private final List<TemplateSetReference<String>> examples;
+    private final Map<String, String> options;
+    private final Set<PwmSettingFlag> flags;
+    private final Map<PwmSettingProperty, String> properties;
+    private final Collection<LDAPPermissionInfo> ldapPermissionInfo;
+    private final boolean required;
+    private final boolean hidden;
+    private final int level;
+    private final Pattern pattern;
+
+    private static Map<PwmSetting, PwmSettingMetaData> initMap()
+    {
+        final EnumMap<PwmSetting, PwmSettingMetaData> map = new EnumMap<>( PwmSetting.class );
+
+        for ( final XmlElement settingElement : PwmSettingXml.readAllSettingXmlElements() )
+        {
+            final PwmSetting pwmSetting = PwmSetting.forKey( settingElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_KEY )
+                    .orElseThrow() ).orElseThrow();
+
+            final PwmSettingMetaData pwmSettingMetaData = PwmSettingMetaData.builder()
+                    .examples( InitReader.readExamples( settingElement ) )
+                    .properties( InitReader.readProperties( pwmSetting, settingElement ) )
+                    .options( InitReader.readOptions( pwmSetting, settingElement ) )
+                    .flags( InitReader.readFlags( settingElement ) )
+                    .ldapPermissionInfo( InitReader.readLdapPermissionInfo( settingElement ) )
+                    .required( InitReader.readRequired( settingElement ) )
+                    .hidden( InitReader.readHidden( pwmSetting, settingElement ) )
+                    .level( InitReader.readLevel( settingElement ) )
+                    .pattern( InitReader.readPattern( pwmSetting, settingElement ) )
+                    .build();
+
+            map.put( pwmSetting, pwmSettingMetaData );
+        }
+
+        return map;
+    }
+
+    static PwmSettingMetaData forSetting( final PwmSetting pwmSetting )
+    {
+        return META_DATA_MAP.get( pwmSetting );
+    }
+
+    private static class InitReader
+    {
+        private static Set<PwmSettingFlag> readFlags( final XmlElement settingElement )
+        {
+            final Set<PwmSettingFlag> returnObj = EnumSet.noneOf( PwmSettingFlag.class );
+            settingElement.getChild( "flag" ).ifPresent( flagElement ->
+                    flagElement.getChildren( "flags" ).forEach( flagsElement ->
+                    {
+                        final String value = flagsElement.getText().orElse( "" ).trim();
+                        JavaHelper.readEnumFromString( PwmSettingFlag.class, value ).ifPresent( returnObj::add );
+                    } )
+            );
+            return Collections.unmodifiableSet( returnObj );
+        }
+
+        private static Map<String, String> readOptions( final PwmSetting pwmSetting, final XmlElement settingElement )
+        {
+            final Map<String, String> returnData = new LinkedHashMap<>();
+            final Optional<XmlElement> optionsElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_OPTIONS );
+            if ( optionsElement.isPresent() )
+            {
+                final List<XmlElement> optionElements = optionsElement.get().getChildren( PwmSettingXml.XML_ELEMENT_OPTION );
+                if ( optionElements != null )
+                {
+                    for ( final XmlElement optionElement : optionElements )
+                    {
+                        final String value = optionElement.getAttribute( PwmSettingXml.XML_ELEMENT_VALUE )
+                                .orElseThrow( () -> new IllegalStateException( "option element is missing 'value' attribute for key " + pwmSetting.getKey() ) );
+
+                        optionElement.getText().ifPresent( textValue ->  returnData.put( value, textValue ) );
+                    }
+                }
+            }
+            return Collections.unmodifiableMap( returnData );
+        }
+
+        private static Collection<LDAPPermissionInfo> readLdapPermissionInfo( final XmlElement settingElement )
+        {
+            final List<LDAPPermissionInfo> returnObj = new ArrayList<>();
+
+            settingElement.getChild( PwmSettingXml.XML_ELEMENT_PERMISSION ).ifPresent( permissionElement ->
+            {
+                permissionElement.getChildren( PwmSettingXml.XML_ELEMENT_LDAP ).forEach( ldapElement ->
+                {
+                    final Optional<LDAPPermissionInfo.Actor> actor = JavaHelper.readEnumFromString(
+                            LDAPPermissionInfo.Actor.class,
+                            permissionElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACTOR ).orElse( "" ) );
+                    final Optional<LDAPPermissionInfo.Access> type = JavaHelper.readEnumFromString(
+                            LDAPPermissionInfo.Access.class,
+                            permissionElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACCESS ).orElse( "" ) );
+
+                    if ( actor.isPresent() && type.isPresent() )
+                    {
+                        returnObj.add( new LDAPPermissionInfo( type.get(), actor.get() ) );
+                    }
+                } );
+            } );
+
+            return Collections.unmodifiableList( returnObj );
+        }
+
+        private static List<TemplateSetReference<String>> readExamples( final XmlElement settingElement )
+        {
+            final List<TemplateSetReference<String>> returnObj = new ArrayList<>();
+            final MacroRequest macroRequest = MacroRequest.forStatic();
+            final List<XmlElement> exampleElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_EXAMPLE );
+            for ( final XmlElement exampleElement : exampleElements )
+            {
+                final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( exampleElement );
+                exampleElement.getText().ifPresent( textValue ->
+                {
+                    final String exampleString = macroRequest.expandMacros( textValue );
+                    returnObj.add( new TemplateSetReference<>( exampleString, Collections.unmodifiableSet( definedTemplates ) ) );
+                } );
+            }
+            if ( returnObj.isEmpty() )
+            {
+                returnObj.add( new TemplateSetReference<>( "", Collections.emptySet() ) );
+            }
+            return Collections.unmodifiableList( returnObj );
+        }
+
+        private static Map<PwmSettingProperty, String> readProperties( final PwmSetting pwmSetting, final XmlElement settingElement )
+        {
+            final Map<PwmSettingProperty, String> newProps = new EnumMap<>( PwmSettingProperty.class );
+            final Optional<XmlElement> propertiesElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_PROPERTIES );
+            if ( propertiesElement.isPresent() )
+            {
+                final List<XmlElement> propertyElements = propertiesElement.get().getChildren( PwmSettingXml.XML_ELEMENT_PROPERTY );
+                if ( propertyElements != null )
+                {
+                    for ( final XmlElement propertyElement : propertyElements )
+                    {
+                        final String keyAttribute = propertyElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_KEY )
+                                .orElseThrow( () -> new IllegalStateException( "property element is missing 'key' attribute for value " + pwmSetting.getKey() ) );
+
+                        final PwmSettingProperty property = JavaHelper.readEnumFromString( PwmSettingProperty.class, keyAttribute )
+                                .orElseThrow( () -> new IllegalStateException( "property element has unknown 'key' attribute for value " + pwmSetting.getKey() ) );
+
+                        propertyElement.getText().ifPresent( value -> newProps.put( property, value ) );
+                    }
+                }
+            }
+            return Collections.unmodifiableMap( newProps );
+        }
+
+        private static boolean readRequired( final XmlElement settingElement )
+        {
+            final String requiredAttribute = settingElement.getAttribute( PwmSettingXml.XML_ELEMENT_REQUIRED ).orElse( "" );
+            return "true".equalsIgnoreCase( requiredAttribute );
+        }
+
+        private static boolean readHidden( final PwmSetting pwmSetting, final XmlElement settingElement )
+        {
+            final String requiredAttribute = settingElement.getAttribute( PwmSettingXml.XML_ELEMENT_HIDDEN ).orElse( "" );
+            return "true".equalsIgnoreCase( requiredAttribute ) || pwmSetting.getCategory().isHidden();
+        }
+
+        private static int readLevel( final XmlElement settingElement )
+        {
+            final String levelAttribute = settingElement.getAttribute( PwmSettingXml.XML_ELEMENT_LEVEL ).orElse( "" );
+            return JavaHelper.silentParseInt( levelAttribute, 0 );
+        }
+
+        private static Pattern readPattern( final PwmSetting pwmSetting, final XmlElement settingElement )
+        {
+            final Optional<XmlElement> regexNode = settingElement.getChild( PwmSettingXml.XML_ELEMENT_REGEX );
+            if ( regexNode.isPresent() )
+            {
+                final Optional<String> regexText = regexNode.get().getText();
+                if ( regexText.isPresent() )
+                {
+                    try
+                    {
+                        return Pattern.compile( regexText.get() );
+                    }
+                    catch ( final PatternSyntaxException e )
+                    {
+                        final String errorMsg = "error compiling regex constraints for setting " + pwmSetting + ", error: " + e.getMessage();
+                        throw new IllegalStateException( errorMsg, e );
+                    }
+                }
+            }
+            return Pattern.compile( ".*", Pattern.DOTALL );
+        }
+    }
+}

+ 0 - 430
server/src/main/java/password/pwm/config/PwmSettingMetaDataReader.java

@@ -1,430 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2021 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;
-
-import org.jrivard.xmlchai.XmlElement;
-import password.pwm.PwmConstants;
-import password.pwm.config.value.PasswordValue;
-import password.pwm.config.value.StoredValue;
-import password.pwm.config.value.ValueFactory;
-import password.pwm.i18n.Config;
-import password.pwm.util.i18n.LocaleHelper;
-import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.LazySupplier;
-import password.pwm.util.java.StringUtil;
-import password.pwm.util.java.TimeDuration;
-import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroRequest;
-
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Supplier;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-public class PwmSettingMetaDataReader
-{
-    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmSettingMetaDataReader.class );
-
-    private final PwmSetting pwmSetting;
-
-    // cached values read from XML file
-    private final Supplier<List<PwmSetting.TemplateSetReference<StoredValue>>> defaultValues = new LazySupplier<>( () -> InternalReader.readDefaultValue( getPwmSetting() ) );
-    private final Supplier<List<PwmSetting.TemplateSetReference<String>>> examples = new LazySupplier<>( () -> InternalReader.readExamples( getPwmSetting() ) );
-    private final Supplier<Map<String, String>> options = new LazySupplier<>( () -> InternalReader.readOptions( getPwmSetting() ) );
-    private final Supplier<Set<PwmSettingFlag>> flags = new LazySupplier<>( () -> InternalReader.readFlags( getPwmSetting() ) );
-    private final Supplier<Map<PwmSettingProperty, String>> properties = new LazySupplier<>( () -> InternalReader.readProperties( getPwmSetting() ) );
-    private final Supplier<Collection<LDAPPermissionInfo>> ldapPermissionInfo = new LazySupplier<>( () -> InternalReader.readLdapPermissionInfo( getPwmSetting() ) );
-    private final Supplier<Boolean> required = new LazySupplier<>( () -> InternalReader.readRequired( getPwmSetting() ) );
-    private final Supplier<Boolean> hidden = new LazySupplier<>( () -> InternalReader.readHidden( getPwmSetting() ) );
-    private final Supplier<Integer> level = new LazySupplier<>( () -> InternalReader.readLevel( getPwmSetting() ) );
-    private final Supplier<Pattern> pattern = new LazySupplier<>( () -> InternalReader.readPattern( getPwmSetting() ) );
-    private final Supplier<String> defaultLocaleLabel = new LazySupplier<>( () -> InternalReader.readLabel( getPwmSetting(), PwmConstants.DEFAULT_LOCALE ) );
-    private final Supplier<String> defaultLocaleDescription = new LazySupplier<>( () -> InternalReader.readDescription( getPwmSetting(), PwmConstants.DEFAULT_LOCALE ) );
-    private final Supplier<String> defaultMenuLocation = new LazySupplier<>( () -> InternalReader.readMenuLocationDebugDefault( getPwmSetting() ) );
-
-    public PwmSettingMetaDataReader( final PwmSetting pwmSetting )
-    {
-        this.pwmSetting = pwmSetting;
-    }
-
-    public PwmSetting getPwmSetting()
-    {
-        return pwmSetting;
-    }
-
-    public Map<PwmSettingProperty, String> getProperties( )
-    {
-        return properties.get();
-    }
-
-    public Set<PwmSettingFlag> getFlags( )
-    {
-        return flags.get();
-    }
-
-    public Map<String, String> getOptions()
-    {
-        return options.get();
-    }
-
-
-    public String getLabel( final Locale locale )
-    {
-        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) )
-        {
-            return defaultLocaleLabel.get();
-        }
-
-        return InternalReader.readLabel( pwmSetting, locale );
-    }
-
-    public String getDescription( final Locale locale )
-    {
-        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) )
-        {
-            return defaultLocaleDescription.get();
-        }
-
-        return InternalReader.readDescription( pwmSetting, locale );
-    }
-
-    public String getExample( final PwmSettingTemplateSet template )
-    {
-         return PwmSetting.TemplateSetReference.referenceForTempleSet( examples.get(), template );
-    }
-
-    public boolean isRequired( )
-    {
-        return required.get();
-    }
-
-    public boolean isHidden( )
-    {
-        return hidden.get();
-    }
-
-    public int getLevel( )
-    {
-        return level.get();
-    }
-
-    public Pattern getRegExPattern( )
-    {
-        return pattern.get();
-    }
-
-    public Collection<LDAPPermissionInfo> getLDAPPermissionInfo()
-    {
-        return ldapPermissionInfo.get();
-    }
-
-    List<PwmSetting.TemplateSetReference<StoredValue>> getDefaultValue()
-    {
-        return defaultValues.get();
-    }
-
-    public String toMenuLocationDebug(
-            final String profileID,
-            final Locale locale
-    )
-    {
-        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) && StringUtil.isEmpty( profileID ) )
-        {
-            return defaultMenuLocation.get();
-        }
-        else
-        {
-            return InternalReader.readMenuLocationDebug( pwmSetting, profileID, locale );
-        }
-    }
-
-    /**
-     * Not required for normal operation, but executing this gets all the enum values populated form XML source.  If run prior to users accessing the settings
-     * module (particularly the config editor) it will increase the initial load performance significantly.  There are no side effects to calling this operation
-     * other than cache population.
-     */
-    public static void initCache()
-    {
-        final Instant startTime = Instant.now();
-        for ( final PwmSetting pwmSetting : EnumSet.allOf( PwmSetting.class ) )
-        {
-            pwmSetting.getProperties();
-            pwmSetting.getFlags();
-            pwmSetting.getOptions();
-            pwmSetting.getLabel( PwmConstants.DEFAULT_LOCALE );
-            pwmSetting.getDescription( PwmConstants.DEFAULT_LOCALE );
-            pwmSetting.getExample( PwmSettingTemplateSet.getDefault() );
-            pwmSetting.isRequired();
-            pwmSetting.isHidden();
-            pwmSetting.getLevel();
-            pwmSetting.getRegExPattern();
-            pwmSetting.getLDAPPermissionInfo();
-            pwmSetting.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
-        }
-        for ( final PwmSettingCategory pwmSettingCategory : EnumSet.allOf( PwmSettingCategory.class ) )
-        {
-            pwmSettingCategory.getLabel( PwmConstants.DEFAULT_LOCALE );
-            pwmSettingCategory.getDescription( PwmConstants.DEFAULT_LOCALE );
-            pwmSettingCategory.isHidden();
-            pwmSettingCategory.getLevel();
-            pwmSettingCategory.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
-            pwmSettingCategory.getScope();
-            pwmSettingCategory.getChildren();
-            pwmSettingCategory.getLevel();
-            pwmSettingCategory.getSettings();
-        }
-        LOGGER.trace( () -> "completed PwmSetting xml cache initialization", TimeDuration.fromCurrent( startTime ) );
-    }
-
-    private static class InternalReader
-    {
-        private static Set<PwmSettingFlag> readFlags( final PwmSetting pwmSetting )
-        {
-            final Set<PwmSettingFlag> returnObj = EnumSet.noneOf( PwmSettingFlag.class );
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final List<XmlElement> flagElements = settingElement.getChildren( "flag" );
-            for ( final XmlElement flagElement : flagElements )
-            {
-                final String value = flagElement.getText().orElse( "" ).trim();
-
-                try
-                {
-                    final PwmSettingFlag flag = PwmSettingFlag.valueOf( value );
-                    returnObj.add( flag );
-                }
-                catch ( final IllegalArgumentException e )
-                {
-                    LOGGER.error( () -> "unknown flag for setting " + pwmSetting.getKey() + ", error: unknown flag value: " + value );
-                }
-
-            }
-            return Collections.unmodifiableSet( returnObj );
-        }
-
-        private static Map<String, String> readOptions( final PwmSetting pwmSetting )
-        {
-            final Map<String, String> returnData = new LinkedHashMap<>();
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final Optional<XmlElement> optionsElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_OPTIONS );
-            if ( optionsElement.isPresent() )
-            {
-                final List<XmlElement> optionElements = optionsElement.get().getChildren( PwmSettingXml.XML_ELEMENT_OPTION );
-                if ( optionElements != null )
-                {
-                    for ( final XmlElement optionElement : optionElements )
-                    {
-                        final String value = optionElement.getAttribute( PwmSettingXml.XML_ELEMENT_VALUE )
-                                .orElseThrow( () -> new IllegalStateException( "option element is missing 'value' attribute for key " + pwmSetting.getKey() ) );
-
-                        optionElement.getText().ifPresent( textValue ->  returnData.put( value, textValue ) );
-                    }
-                }
-            }
-            return Collections.unmodifiableMap( returnData );
-        }
-
-        private static Collection<LDAPPermissionInfo> readLdapPermissionInfo( final PwmSetting pwmSetting )
-        {
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final List<XmlElement> permissionElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_LDAP_PERMISSION );
-            final List<LDAPPermissionInfo> returnObj = new ArrayList<>();
-            if ( permissionElements != null )
-            {
-                for ( final XmlElement permissionElement : permissionElements )
-                {
-                    final Optional<LDAPPermissionInfo.Actor> actor = JavaHelper.readEnumFromString(
-                            LDAPPermissionInfo.Actor.class,
-                            permissionElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACTOR ).orElse( "" )
-                    );
-                    final Optional<LDAPPermissionInfo.Access> type = JavaHelper.readEnumFromString(
-                            LDAPPermissionInfo.Access.class,
-                            permissionElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACCESS ).orElse( "" )
-                    );
-                    if ( actor.isPresent() && type.isPresent() )
-                    {
-                        final LDAPPermissionInfo permissionInfo = new LDAPPermissionInfo( type.get(), actor.get() );
-                        returnObj.add( permissionInfo );
-                    }
-                }
-            }
-            return Collections.unmodifiableList( returnObj );
-        }
-
-        private static List<PwmSetting.TemplateSetReference<String>> readExamples( final PwmSetting pwmSetting )
-        {
-            final List<PwmSetting.TemplateSetReference<String>> returnObj = new ArrayList<>();
-            final MacroRequest macroRequest = MacroRequest.forStatic();
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final List<XmlElement> exampleElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_EXAMPLE );
-            for ( final XmlElement exampleElement : exampleElements )
-            {
-                final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( exampleElement );
-                exampleElement.getText().ifPresent( textValue ->
-                {
-                    final String exampleString = macroRequest.expandMacros( textValue );
-                    returnObj.add( new PwmSetting.TemplateSetReference<>( exampleString, Collections.unmodifiableSet( definedTemplates ) ) );
-                } );
-            }
-            if ( returnObj.isEmpty() )
-            {
-                returnObj.add( new PwmSetting.TemplateSetReference<>( "", Collections.emptySet() ) );
-            }
-            return Collections.unmodifiableList( returnObj );
-        }
-
-        private static Map<PwmSettingProperty, String> readProperties( final PwmSetting pwmSetting )
-        {
-            final Map<PwmSettingProperty, String> newProps = new EnumMap<>( PwmSettingProperty.class );
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final Optional<XmlElement> propertiesElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_PROPERTIES );
-            if ( propertiesElement.isPresent() )
-            {
-                final List<XmlElement> propertyElements = propertiesElement.get().getChildren( PwmSettingXml.XML_ELEMENT_PROPERTY );
-                if ( propertyElements != null )
-                {
-                    for ( final XmlElement propertyElement : propertyElements )
-                    {
-                        final String keyAttribute = propertyElement.getAttribute( PwmSettingXml.XML_ATTRIBUTE_KEY )
-                                .orElseThrow( () -> new IllegalStateException( "property element is missing 'key' attribute for value " + pwmSetting.getKey() ) );
-
-                        final PwmSettingProperty property = JavaHelper.readEnumFromString( PwmSettingProperty.class, keyAttribute )
-                                .orElseThrow( () -> new IllegalStateException( "property element has unknown 'key' attribute for value " + pwmSetting.getKey() ) );
-
-                        propertyElement.getText().ifPresent( value -> newProps.put( property, value ) );
-                    }
-                }
-            }
-            return Collections.unmodifiableMap( newProps );
-        }
-
-        private static List<PwmSetting.TemplateSetReference<StoredValue>> readDefaultValue( final PwmSetting pwmSetting )
-        {
-            final List<PwmSetting.TemplateSetReference<StoredValue>> returnObj = new ArrayList<>();
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final List<XmlElement> defaultElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_DEFAULT );
-            if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD )
-            {
-                returnObj.add( new PwmSetting.TemplateSetReference<>( new PasswordValue( null ), Collections.emptySet() ) );
-            }
-            else
-            {
-                for ( final XmlElement defaultElement : defaultElements )
-                {
-                    final Set<PwmSettingTemplate> definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement );
-                    final StoredValue storedValue = ValueFactory.fromXmlValues( pwmSetting, defaultElement, null );
-                    returnObj.add( new PwmSetting.TemplateSetReference<>( storedValue, definedTemplates ) );
-                }
-            }
-            if ( returnObj.isEmpty() )
-            {
-                throw new IllegalStateException( "no default value for setting " + pwmSetting.getKey() );
-            }
-            return Collections.unmodifiableList( returnObj );
-        }
-
-
-        private static boolean readRequired( final PwmSetting pwmSetting )
-        {
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final String requiredAttribute = settingElement.getAttribute( PwmSettingXml.XML_ELEMENT_REQUIRED ).orElse( "" );
-            return "true".equalsIgnoreCase( requiredAttribute );
-        }
-
-        private static boolean readHidden( final PwmSetting pwmSetting )
-        {
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final String requiredAttribute = settingElement.getAttribute( PwmSettingXml.XML_ELEMENT_HIDDEN ).orElse( "" );
-            return "true".equalsIgnoreCase( requiredAttribute ) || pwmSetting.getCategory().isHidden();
-        }
-
-        private static int readLevel( final PwmSetting pwmSetting )
-        {
-            final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting );
-            final String levelAttribute = settingElement.getAttribute( PwmSettingXml.XML_ELEMENT_LEVEL ).orElse( "" );
-            return JavaHelper.silentParseInt( levelAttribute, 0 );
-        }
-
-        private static Pattern readPattern( final PwmSetting pwmSetting )
-        {
-            final XmlElement settingNode = PwmSettingXml.readSettingXml( pwmSetting );
-            final Optional<XmlElement> regexNode = settingNode.getChild( PwmSettingXml.XML_ELEMENT_REGEX );
-            if ( regexNode.isPresent() )
-            {
-                final Optional<String> regexText = regexNode.get().getText();
-                if ( regexText.isPresent() )
-                {
-                    try
-                    {
-                        return Pattern.compile( regexText.get() );
-                    }
-                    catch ( final PatternSyntaxException e )
-                    {
-                        final String errorMsg = "error compiling regex constraints for setting " + pwmSetting + ", error: " + e.getMessage();
-                        LOGGER.error( () -> errorMsg, e );
-                        throw new IllegalStateException( errorMsg, e );
-                    }
-                }
-            }
-            return Pattern.compile( ".*", Pattern.DOTALL );
-        }
-
-        private static String readMenuLocationDebugDefault( final PwmSetting pwmSetting )
-        {
-            final Locale locale = PwmConstants.DEFAULT_LOCALE;
-            final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
-            return pwmSetting.getCategory().toMenuLocationDebug( null, locale ) + separator + pwmSetting.getLabel( locale );
-        }
-
-        private static String readMenuLocationDebug( final PwmSetting pwmSetting, final String profileID, final Locale locale )
-        {
-            final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
-            return pwmSetting.getCategory().toMenuLocationDebug( profileID, locale ) + separator + pwmSetting.getLabel( locale );
-        }
-
-        private static String readLabel( final PwmSetting pwmSetting, final Locale locale )
-        {
-            return readStringProperty( password.pwm.i18n.PwmSetting.SETTING_LABEL_PREFIX + pwmSetting.getKey(), locale );
-        }
-
-        private static String readDescription( final PwmSetting pwmSetting, final Locale locale )
-        {
-            return readStringProperty( password.pwm.i18n.PwmSetting.SETTING_DESCRIPTION_PREFIX + pwmSetting.getKey(), locale );
-        }
-
-        private static String readStringProperty( final String key, final Locale locale )
-        {
-            final String storedText = LocaleHelper.getLocalizedMessage( locale, key, null, password.pwm.i18n.PwmSetting.class );
-            final MacroRequest macroRequest = MacroRequest.forStatic();
-            return macroRequest.expandMacros( storedText );
-        }
-    }
-}

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

@@ -66,7 +66,7 @@ public enum PwmSettingTemplate
     public boolean isHidden( )
     {
         final XmlElement templateElement = readTemplateElement( this );
-        final Optional<String> requiredAttribute = templateElement.getAttribute( "hidden" );
+        final Optional<String> requiredAttribute = templateElement.getAttribute( PwmSettingXml.XML_ELEMENT_HIDDEN );
         return requiredAttribute.isPresent() && "true".equalsIgnoreCase( requiredAttribute.get() );
     }
 

+ 4 - 13
server/src/main/java/password/pwm/config/PwmSettingTemplateSet.java

@@ -24,8 +24,6 @@ import lombok.Value;
 import password.pwm.util.java.CollectionUtil;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
@@ -47,7 +45,7 @@ public class PwmSettingTemplateSet implements Serializable
         workingSet.addAll( EnumSet.allOf( PwmSettingTemplate.Type.class ).stream()
                 .filter( type -> !seenTypes.contains( type ) )
                 .map( PwmSettingTemplate.Type::getDefaultValue )
-                .collect( Collectors.toSet( ) ) );
+                .collect( Collectors.toUnmodifiableSet( ) ) );
 
         this.templates = Set.copyOf( workingSet );
     }
@@ -73,15 +71,8 @@ public class PwmSettingTemplateSet implements Serializable
      */
     public static List<PwmSettingTemplateSet> allValues()
     {
-        final List<PwmSettingTemplateSet> templateSets = new ArrayList<>();
-
-        for ( final PwmSettingTemplate template : EnumSet.allOf( PwmSettingTemplate.class ) )
-        {
-            final PwmSettingTemplateSet templateSet = new PwmSettingTemplateSet( Collections.singleton( template ) );
-            templateSets.add( templateSet );
-        }
-
-        templateSets.add( getDefault() );
-        return Collections.unmodifiableList( templateSets );
+        return EnumSet.allOf( PwmSettingTemplate.class ).stream()
+                .map( pwmSettingTemplate -> new PwmSettingTemplateSet( Set.of( pwmSettingTemplate ) ) )
+                .collect( Collectors.toUnmodifiableList() );
     }
 }

+ 6 - 18
server/src/main/java/password/pwm/config/PwmSettingXml.java

@@ -34,6 +34,7 @@ import java.io.InputStream;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -42,7 +43,8 @@ public class PwmSettingXml
     public static final String SETTING_XML_FILENAME = ( PwmSetting.class.getPackage().getName()
             + "." + PwmSetting.class.getSimpleName() ).replace( '.', '/' ) + ".xml";
 
-    static final String XML_ELEMENT_LDAP_PERMISSION = "ldapPermission";
+    static final String XML_ELEMENT_PERMISSION = "permission";
+    static final String XML_ELEMENT_LDAP = "ldap";
     static final String XML_ELEMENT_EXAMPLE = "example";
     static final String XML_ELEMENT_DEFAULT = "default";
 
@@ -60,6 +62,7 @@ public class PwmSettingXml
     static final String XML_ELEMENT_OPTION = "option";
     static final String XML_ELEMENT_OPTIONS = "options";
     static final String XML_ELEMENT_SCOPE = "scope";
+    static final String XML_ELEMENT_SETTING = "setting";
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmSettingXml.class );
 
@@ -82,26 +85,11 @@ public class PwmSettingXml
         }
     }
 
-    /*
-    private static void validateXmlSchema( )
+    static List<XmlElement> readAllSettingXmlElements()
     {
-        try
-        {
-            final InputStream xsdInputStream = PwmSetting.class.getClassLoader().getResourceAsStream( "password/pwm/config/PwmSetting.xsd" );
-            final InputStream xmlInputStream = PwmSetting.class.getClassLoader().getResourceAsStream( "password/pwm/config/PwmSetting.xml" );
-            final SchemaFactory factory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
-            final Schema schema = factory.newSchema( new StreamSource( xsdInputStream ) );
-            final Validator validator = schema.newValidator();
-            validator.validate( new StreamSource( xmlInputStream ) );
-        }
-        catch ( final Exception e )
-        {
-            throw new IllegalStateException( "error validating PwmSetting.xml schema using PwmSetting.xsd definition: " + e.getMessage() );
-        }
+        return XML_DOC_CACHE.get().getRootElement().getChildren( XML_ELEMENT_SETTING );
     }
 
-     */
-
     static XmlElement readSettingXml( final PwmSetting setting )
     {
         final String expression = "/settings/setting[@key=\"" + setting.getKey() + "\"]";

+ 70 - 0
server/src/main/java/password/pwm/config/TemplateSetReference.java

@@ -0,0 +1,70 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 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;
+
+import lombok.Value;
+import password.pwm.util.java.CollectionUtil;
+
+import java.util.List;
+import java.util.Set;
+
+@Value
+class TemplateSetReference<T>
+{
+    private final T reference;
+    private final Set<PwmSettingTemplate> settingTemplates;
+
+    static <T> T referenceForTempleSet(
+            final List<TemplateSetReference<T>> templateSetReferences,
+            final PwmSettingTemplateSet pwmSettingTemplateSet
+    )
+    {
+        final PwmSettingTemplateSet effectiveTemplateSet = pwmSettingTemplateSet == null
+                ? PwmSettingTemplateSet.getDefault()
+                : pwmSettingTemplateSet;
+
+        if ( templateSetReferences == null || templateSetReferences.isEmpty() )
+        {
+            throw new IllegalStateException( "templateSetReferences can not be null" );
+        }
+
+        if ( templateSetReferences.size() == 1 )
+        {
+            return templateSetReferences.get( 0 ).getReference();
+        }
+
+        for ( int matchCountExamSize = templateSetReferences.size(); matchCountExamSize > 0; matchCountExamSize-- )
+        {
+            for ( final TemplateSetReference<T> templateSetReference : templateSetReferences )
+            {
+                final Set<PwmSettingTemplate> temporarySet = CollectionUtil.copyToEnumSet( templateSetReference.getSettingTemplates(), PwmSettingTemplate.class );
+                temporarySet.retainAll( effectiveTemplateSet.getTemplates() );
+                final int matchCount = temporarySet.size();
+                if ( matchCount == matchCountExamSize )
+                {
+                    return templateSetReference.getReference();
+                }
+            }
+        }
+
+        return templateSetReferences.get( 0 ).getReference();
+    }
+}

+ 1 - 1
server/src/main/java/password/pwm/health/LDAPHealthChecker.java

@@ -903,7 +903,7 @@ public class LDAPHealthChecker implements HealthSupplier
             {
                 if ( !pwmSetting.isHidden()
                         && pwmSetting.getCategory() == PwmSettingCategory.LDAP_PROFILE
-                        && pwmSetting.getFlags().contains( PwmSettingFlag.ldapDNsyntax )
+                        && pwmSetting.getFlags().contains( PwmSettingFlag.ldapDnSyntax )
                 )
                 {
                     for ( final String profile : config.getLdapProfiles().keySet() )

+ 4 - 1
server/src/main/java/password/pwm/http/PwmSession.java

@@ -280,7 +280,10 @@ public class PwmSession implements Serializable
             this.getLoginInfoBean().setAuthenticated( false );
 
             // close out any outstanding connections
-            getClientConnectionHolder( pwmRequest.getPwmApplication() ).closeConnections();
+            if ( pwmRequest != null && clientConnectionHolder != null )
+            {
+                getClientConnectionHolder( pwmRequest.getPwmApplication() ).closeConnections();
+            }
             clientConnectionHolder = null;
 
             LOGGER.debug( pwmRequest, sb::toString );

File diff suppressed because it is too large
+ 330 - 153
server/src/main/resources/password/pwm/config/PwmSetting.xml


+ 136 - 26
server/src/main/resources/password/pwm/config/PwmSetting.xsd

@@ -19,17 +19,24 @@
   ~ limitations under the License.
   -->
 
-<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
+<xs:schema
+        attributeFormDefault="unqualified"
+        elementFormDefault="qualified"
+        xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xs:element name="settings">
         <xs:complexType>
             <xs:sequence>
                 <xs:element name="setting" maxOccurs="unbounded" minOccurs="0">
                     <xs:complexType>
-                        <xs:choice maxOccurs="unbounded" minOccurs="0">
-                            <xs:element type="xs:string" name="label"/>
-                            <xs:element type="xs:string" name="description"/>
-                            <xs:element type="xs:string" name="regex"/>
-                            <xs:element name="default">
+                        <xs:sequence>
+                            <xs:element name="flags" minOccurs="0" maxOccurs="1">
+                                <xs:complexType>
+                                    <xs:sequence>
+                                        <xs:element type="Flag_Type" name="flag" minOccurs="1" maxOccurs="unbounded"/>
+                                    </xs:sequence>
+                                </xs:complexType>
+                            </xs:element>
+                            <xs:element name="default" minOccurs="1" maxOccurs="unbounded">
                                 <xs:complexType mixed="true">
                                     <xs:sequence>
                                         <xs:element name="value" maxOccurs="unbounded" minOccurs="0">
@@ -42,10 +49,17 @@
                                             </xs:complexType>
                                         </xs:element>
                                     </xs:sequence>
+                                    <xs:attribute name="syntaxVersion" use="optional">
+                                        <xs:simpleType>
+                                            <xs:restriction base="xs:integer">
+                                                <xs:minInclusive value="0" />
+                                            </xs:restriction>
+                                        </xs:simpleType>
+                                    </xs:attribute>
                                     <xs:attribute type="xs:string" name="template" use="optional"/>
                                 </xs:complexType>
                             </xs:element>
-                            <xs:element name="options">
+                            <xs:element name="options" maxOccurs="1" minOccurs="0">
                                 <xs:complexType>
                                     <xs:sequence>
                                         <xs:element name="option" maxOccurs="unbounded" minOccurs="0">
@@ -60,33 +74,129 @@
                                     </xs:sequence>
                                 </xs:complexType>
                             </xs:element>
-                        </xs:choice>
-                        <xs:attribute type="xs:string" name="key" use="optional"/>
-                        <xs:attribute type="xs:byte" name="level" use="optional"/>
-                        <xs:attribute type="xs:string" name="required" use="optional"/>
-                        <xs:attribute type="xs:string" name="hidden" use="optional"/>
-                    </xs:complexType>
-                </xs:element>
-                <xs:element name="category" maxOccurs="unbounded" minOccurs="0">
-                    <xs:complexType>
-                        <xs:sequence>
-                            <xs:element type="xs:string" name="label"/>
-                            <xs:element type="xs:string" name="description"/>
+                            <xs:element name="properties" maxOccurs="1" minOccurs="0">
+                                <xs:complexType>
+                                    <xs:sequence>
+                                        <xs:element name="property" maxOccurs="unbounded" minOccurs="1">
+                                            <xs:complexType>
+                                                <xs:simpleContent>
+                                                    <xs:extension base="xs:string">
+                                                        <xs:attribute type="xs:string" name="key" use="optional"/>
+                                                    </xs:extension>
+                                                </xs:simpleContent>
+                                            </xs:complexType>
+                                        </xs:element>
+                                    </xs:sequence>
+                                </xs:complexType>
+                            </xs:element>
+                            <xs:element name="example" minOccurs="0" maxOccurs="unbounded">
+                                <xs:complexType>
+                                    <xs:simpleContent>
+                                        <xs:extension base="xs:string">
+                                            <xs:attribute type="xs:string" name="template" use="optional"/>
+                                        </xs:extension>
+                                    </xs:simpleContent>
+                                </xs:complexType>
+                            </xs:element>
+                            <xs:element type="xs:string" name="regex" minOccurs="0" maxOccurs="1"/>
+                            <xs:element name="permission" maxOccurs="1" minOccurs="0">
+                                <xs:complexType>
+                                    <xs:sequence>
+                                        <xs:element name="ldap" minOccurs="1" maxOccurs="unbounded">
+                                            <xs:complexType>
+                                                <xs:attribute type="Actor_Type" name="actor" use="required"/>
+                                                <xs:attribute type="Access_Type" name="access" use="required"/>
+                                            </xs:complexType>
+                                        </xs:element>
+                                    </xs:sequence>
+                                </xs:complexType>
+                            </xs:element>
                         </xs:sequence>
-                        <xs:attribute type="xs:string" name="key" use="optional"/>
-                        <xs:attribute type="xs:byte" name="level" use="optional"/>
-                        <xs:attribute type="xs:string" name="hidden" use="optional"/>
+                        <xs:attribute type="xs:string" name="key" use="required"/>
+                        <xs:attribute type="xs:boolean" name="hidden" use="optional"/>
+                        <xs:attribute type="Level_Type" name="level" use="optional"/>
+                        <xs:attribute type="xs:boolean" name="required" use="optional"/>
                     </xs:complexType>
                 </xs:element>
-                <xs:element name="template" maxOccurs="unbounded" minOccurs="0">
-                    <xs:complexType>
+                <xs:element name="category" maxOccurs="unbounded" minOccurs="0">
+                    <xs:complexType mixed="true">
                         <xs:sequence>
-                            <xs:element type="xs:string" name="label"/>
+                            <xs:element name="profile" minOccurs="0">
+                                <xs:complexType>
+                                    <xs:simpleContent>
+                                        <xs:extension base="xs:string">
+                                            <xs:attribute type="xs:string" name="setting" use="optional"/>
+                                        </xs:extension>
+                                    </xs:simpleContent>
+                                </xs:complexType>
+                            </xs:element>
                         </xs:sequence>
-                        <xs:attribute type="xs:string" name="key" use="optional"/>
+                        <xs:attribute type="xs:boolean" name="hidden" use="required"/>
+                        <xs:attribute type="Scope_Type" name="scope" use="required"/>
+                        <xs:attribute type="xs:string" name="key" use="required"/>
+                        <xs:attribute type="Level_Type" name="level" use="optional"/>
                     </xs:complexType>
                 </xs:element>
             </xs:sequence>
         </xs:complexType>
+        <xs:unique name="unique-setting-key">
+            <xs:selector xpath="setting"/>
+            <xs:field xpath="@key"/>
+        </xs:unique>
+        <xs:unique name="unique-category-key">
+            <xs:selector xpath="category"/>
+            <xs:field xpath="@key"/>
+        </xs:unique>
     </xs:element>
+    <xs:simpleType name="Actor_Type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="self" />
+            <xs:enumeration value="self_other" />
+            <xs:enumeration value="proxy" />
+            <xs:enumeration value="guestManager" />
+            <xs:enumeration value="helpdesk" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="Access_Type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="read" />
+            <xs:enumeration value="write" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="Level_Type">
+        <xs:restriction base="xs:integer">
+            <xs:minInclusive value="0" />
+            <xs:maxInclusive value="2" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="Scope_Type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="SYSTEM" />
+            <xs:enumeration value="DOMAIN" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="Flag_Type">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="MacroSupport" />
+            <xs:enumeration value="ldapDnSyntax" />
+            <xs:enumeration value="emailSyntax" />
+            <xs:enumeration value="NoDefault" />
+            <xs:enumeration value="MultiDomain" />
+            <xs:enumeration value="ReloadEditorOnModify" />
+            <xs:enumeration value="Select_AllowUserInput" />
+            <xs:enumeration value="Permission_HideGroups" />
+            <xs:enumeration value="Permission_HideMatch" />
+            <xs:enumeration value="Form_HideOptions" />
+            <xs:enumeration value="Form_ShowUniqueOption" />
+            <xs:enumeration value="Form_ShowReadOnlyOption" />
+            <xs:enumeration value="Form_ShowRequiredOption" />
+            <xs:enumeration value="Form_ShowMultiValueOption" />
+            <xs:enumeration value="Form_HideStandardOptions" />
+            <xs:enumeration value="Form_ShowSource" />
+            <xs:enumeration value="Verification_HideMinimumOptional" />
+            <xs:enumeration value="WebService_NoBody" />
+            <xs:enumeration value="Sorted" />
+            <xs:enumeration value="Deprecated" />
+        </xs:restriction>
+    </xs:simpleType>
 </xs:schema>

+ 19 - 0
server/src/test/java/password/pwm/config/PwmSettingCategoryTest.java

@@ -24,6 +24,8 @@ import org.junit.Assert;
 import org.junit.Test;
 import password.pwm.PwmConstants;
 
+import java.util.EnumSet;
+
 public class PwmSettingCategoryTest
 {
     @Test
@@ -93,4 +95,21 @@ public class PwmSettingCategoryTest
                 Assert.assertNotNull( scope );
         }
     }
+
+    @Test
+    public void testAllCategoryMethods()
+    {
+        for ( final PwmSettingCategory pwmSettingCategory : EnumSet.allOf( PwmSettingCategory.class ) )
+        {
+            pwmSettingCategory.getLabel( PwmConstants.DEFAULT_LOCALE );
+            pwmSettingCategory.getDescription( PwmConstants.DEFAULT_LOCALE );
+            pwmSettingCategory.isHidden();
+            pwmSettingCategory.getLevel();
+            pwmSettingCategory.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
+            pwmSettingCategory.getScope();
+            pwmSettingCategory.getChildren();
+            pwmSettingCategory.getLevel();
+            pwmSettingCategory.getSettings();
+        }
+    }
 }

+ 0 - 33
server/src/test/java/password/pwm/config/PwmSettingMetaDataReaderTest.java

@@ -1,33 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2021 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;
-
-import org.junit.Test;
-
-public class PwmSettingMetaDataReaderTest
-{
-    @Test
-    public void initCache()
-    {
-        // just checking that no exceptions are thrown
-        PwmSettingMetaDataReader.initCache();
-    }
-}

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

@@ -41,6 +41,7 @@ import password.pwm.util.secure.PwmSecurityKey;
 
 import java.io.InputStream;
 import java.io.Serializable;
+import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -59,10 +60,13 @@ public class PwmSettingTest
                 .pwmSecurityKey( pwmSecurityKey )
                 .storedValueEncoderMode( StoredValueEncoder.Mode.ENCODED )
                 .build();
+
+        final List<PwmSettingTemplateSet> allPwmSettingTemplates = PwmSettingTemplateSet.allValues();
+
         for ( final PwmSetting pwmSetting : PwmSetting.values() )
         {
             //System.out.println( pwmSetting.name() + " " + pwmSetting.getKey()  );
-            for ( final PwmSettingTemplateSet templateSet : PwmSettingTemplateSet.allValues() )
+            for ( final PwmSettingTemplateSet templateSet : allPwmSettingTemplates )
             {
                 final StoredValue storedValue = pwmSetting.getDefaultValue( templateSet );
                 storedValue.toNativeObject();
@@ -212,4 +216,28 @@ public class PwmSettingTest
         final List<PwmSetting> list = PwmSetting.sortedValues();
         Assert.assertEquals( list.size(), PwmSetting.values().length );
     }
+
+
+    @Test
+    public void testAllSettingMethods()
+    {
+        for ( final PwmSetting pwmSetting : EnumSet.allOf( PwmSetting.class ) )
+        {
+            pwmSetting.getProperties();
+            pwmSetting.getFlags();
+            pwmSetting.getOptions();
+            pwmSetting.getLabel( PwmConstants.DEFAULT_LOCALE );
+            pwmSetting.getDescription( PwmConstants.DEFAULT_LOCALE );
+            pwmSetting.isRequired();
+            pwmSetting.isHidden();
+            pwmSetting.getLevel();
+            pwmSetting.getRegExPattern();
+            pwmSetting.getLDAPPermissionInfo();
+            PwmSettingTemplateSet.allValues().forEach( pwmSetting::getDefaultValue );
+            PwmSettingTemplateSet.allValues().forEach( pwmSetting::getExample );
+
+            pwmSetting.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
+        }
+    }
+
 }

+ 25 - 0
server/src/test/java/password/pwm/config/PwmSettingXmlTest.java

@@ -28,8 +28,14 @@ import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.xml.sax.SAXParseException;
 import password.pwm.util.java.JavaHelper;
 
+import javax.xml.XMLConstants;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
 import java.io.InputStream;
 import java.util.List;
 import java.util.Optional;
@@ -124,6 +130,25 @@ public class PwmSettingXmlTest
             Assert.assertTrue( errorMsg, setting.isPresent() );
         }
     }
+
+    @Test
+    public void testPwmSettingXmlFileSchema()
+            throws Exception
+    {
+        try
+        {
+            final InputStream xsdInputStream = PwmSetting.class.getClassLoader().getResourceAsStream( "password/pwm/config/PwmSetting.xsd" );
+            final InputStream xmlInputStream = PwmSetting.class.getClassLoader().getResourceAsStream( "password/pwm/config/PwmSetting.xml" );
+            final SchemaFactory factory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
+            final Schema schema = factory.newSchema( new StreamSource( xsdInputStream ) );
+            final Validator validator = schema.newValidator();
+            validator.validate( new StreamSource( xmlInputStream ) );
+        }
+        catch ( final SAXParseException e )
+        {
+            Assert.fail( "PwmSetting.xml schema violation: " + e.toString() );
+        }
+    }
 }
 
 

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

@@ -228,7 +228,7 @@ StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
     editorOptions['completeFunction'] = okAction;
     editorOptions['value'] = iteration > -1 ? PWM_VAR['clientSettingCache'][settingKey][iteration] : '';
 
-    const isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'ldapDNsyntax');
+    const isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'ldapDnSyntax');
     if (isLdapDN) {
         UILibrary.editLdapDN(okAction,{currentDN: editorOptions['value']});
     } else {

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

@@ -868,7 +868,7 @@ StringValueHandler.init = function(settingKey) {
             });
         });
 
-        const isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'], 'ldapDNsyntax');
+        const isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'], 'ldapDnSyntax');
         const editor = function(){
             const writeBackFunc = function(value){
                 PWM_CFGEDIT.writeSetting(settingKey,value,function(){

Some files were not shown because too many files changed in this diff