Преглед изворни кода

improve PwmPasswordPolicy immutabllity

Jason Rivard пре 4 година
родитељ
комит
a3e8e83867

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

@@ -62,7 +62,8 @@ public class AppConfig
     {
         this.storedConfiguration = storedConfiguration;
         this.settingReader = new SettingReader( storedConfiguration, null, null );
-        domainConfigMap = getDomainIDs().stream().collect( Collectors.toUnmodifiableMap(
+        domainConfigMap = getDomainIDs().stream()
+                .collect( Collectors.toUnmodifiableMap(
                 ( domainID ) -> domainID,
                 ( domainID ) -> new DomainConfig( this, domainID ) ) );
     }
@@ -104,7 +105,7 @@ public class AppConfig
                 nonDefaultProperties.put( loopProperty, configuredValue );
             }
         }
-        return nonDefaultProperties;
+        return Collections.unmodifiableMap( nonDefaultProperties );
     }
 
     public StoredConfiguration getStoredConfiguration()

+ 9 - 80
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -20,12 +20,10 @@
 
 package password.pwm.config;
 
-import com.novell.ldapchai.util.StringHelper;
 import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
-import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.option.CertificateMatchingMode;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.MessageSendMethod;
@@ -43,7 +41,6 @@ import password.pwm.config.profile.Profile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.profile.PwmPasswordPolicy;
-import password.pwm.config.profile.PwmPasswordRule;
 import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.stored.StoredConfigItemKey;
@@ -76,6 +73,7 @@ import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumMap;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -243,19 +241,9 @@ public class DomainConfig
 
     public PwmPasswordPolicy getPasswordPolicy( final String profile, final Locale locale )
     {
-        if ( dataCache.cachedPasswordPolicy.containsKey( profile ) && dataCache.cachedPasswordPolicy.get( profile ).containsKey(
-                locale ) )
-        {
-            return dataCache.cachedPasswordPolicy.get( profile ).get( locale );
-        }
-
-        final PwmPasswordPolicy policy = initPasswordPolicy( profile, locale );
-        if ( !dataCache.cachedPasswordPolicy.containsKey( profile ) )
-        {
-            dataCache.cachedPasswordPolicy.put( profile, new LinkedHashMap<>() );
-        }
-        dataCache.cachedPasswordPolicy.get( profile ).put( locale, policy );
-        return policy;
+        return dataCache.cachedPasswordPolicy
+                .computeIfAbsent( profile, s -> new HashMap<>() )
+                .computeIfAbsent( locale, s -> PwmPasswordPolicy.createPwmPasswordPolicy( this, profile, locale ) );
     }
 
     public List<String> getPasswordProfileIDs( )
@@ -263,70 +251,6 @@ public class DomainConfig
         return StoredConfigurationUtil.profilesForSetting( PwmSetting.PASSWORD_PROFILE_LIST, storedConfiguration );
     }
 
-    protected PwmPasswordPolicy initPasswordPolicy( final String profile, final Locale locale )
-    {
-        final Map<String, String> passwordPolicySettings = new LinkedHashMap<>();
-        for ( final PwmPasswordRule rule : PwmPasswordRule.values() )
-        {
-            if ( rule.getPwmSetting() != null || rule.getAppProperty() != null )
-            {
-                final String value;
-                final PwmSetting pwmSetting = rule.getPwmSetting();
-                switch ( rule )
-                {
-                    case DisallowedAttributes:
-                    case DisallowedValues:
-                    case CharGroupsValues:
-                        value = StringHelper.stringCollectionToString(
-                                ValueTypeConverter.valueToStringArray( storedConfiguration.readSetting( pwmSetting, profile ) ), "\n" );
-                        break;
-                    case RegExMatch:
-                    case RegExNoMatch:
-                        value = StringHelper.stringCollectionToString(
-                                ValueTypeConverter.valueToStringArray( storedConfiguration.readSetting( pwmSetting,
-                                        profile ) ), ";;;" );
-                        break;
-                    case ChangeMessage:
-                        value = ValueTypeConverter.valueToLocalizedString(
-                                storedConfiguration.readSetting( pwmSetting, profile ), locale );
-                        break;
-                    case ADComplexityLevel:
-                        value = ValueTypeConverter.valueToEnum(
-                                pwmSetting, storedConfiguration.readSetting( pwmSetting, profile ),
-                                ADPolicyComplexity.class
-                        ).toString();
-                        break;
-                    case AllowMacroInRegExSetting:
-                        value = readAppProperty( AppProperty.ALLOW_MACRO_IN_REGEX_SETTING );
-                        break;
-                    default:
-                        value = String.valueOf(
-                                storedConfiguration.readSetting( pwmSetting, profile ).toNativeObject() );
-                }
-                passwordPolicySettings.put( rule.getKey(), value );
-            }
-        }
-
-        // set case sensitivity
-        final String caseSensitivitySetting = ValueTypeConverter.valueToString( storedConfiguration.readSetting(
-                PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY, null ) );
-        if ( !"read".equals( caseSensitivitySetting ) )
-        {
-            passwordPolicySettings.put( PwmPasswordRule.CaseSensitive.getKey(), caseSensitivitySetting );
-        }
-
-        // set pwm-specific values
-        final List<UserPermission> queryMatch = ( List<UserPermission> ) storedConfiguration.readSetting( PwmSetting.PASSWORD_POLICY_QUERY_MATCH, profile ).toNativeObject();
-        final String ruleText = ValueTypeConverter.valueToLocalizedString( storedConfiguration.readSetting( PwmSetting.PASSWORD_POLICY_RULE_TEXT, profile ), locale );
-
-        final PwmPasswordPolicy.PolicyMetaData policyMetaData = PwmPasswordPolicy.PolicyMetaData.builder()
-                .profileID( profile )
-                .userPermissions( queryMatch )
-                .ruleText( ruleText )
-                .build();
-
-        return  PwmPasswordPolicy.createPwmPasswordPolicy( passwordPolicySettings, null, policyMetaData );
-    }
 
     public List<String> readSettingAsStringArray( final PwmSetting setting )
     {
@@ -459,6 +383,11 @@ public class DomainConfig
         return storedConfiguration.readSetting( setting, null );
     }
 
+    public String getDomainID()
+    {
+        return domainID;
+    }
+
     private class ConfigurationSuppliers
     {
         private final Supplier<Map<String, LdapProfile>> ldapProfilesSupplier = new LazySupplier<>( () ->

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

@@ -21,7 +21,9 @@
 package password.pwm.config;
 
 import password.pwm.bean.PrivateKeyCertificate;
+import password.pwm.config.stored.StoredConfigItemKey;
 import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.ValueTypeConverter;
 import password.pwm.config.value.data.ActionConfiguration;
@@ -31,6 +33,7 @@ import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
 
 import java.security.cert.X509Certificate;
 import java.util.List;
@@ -41,6 +44,8 @@ import java.util.Set;
 
 public class SettingReader
 {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( SettingReader.class );
+
     private final StoredConfiguration storedConfiguration;
     private final String profileID;
     private final String domainID;
@@ -142,6 +147,27 @@ public class SettingReader
 
     private StoredValue readSetting( final PwmSetting setting )
     {
+
+        /*
+        if ( StringUtil.isEmpty( domainID ) )
+        {
+            if ( setting.getCategory().getScope() == PwmSettingScope.DOMAIN )
+            {
+                final String msg = "attempt to read DOMAIN scope setting '" + setting.getKey() + "' via system scope";
+                LOGGER.warn( () -> msg );
+            }
+        }
+        else
+        {
+            if ( setting.getCategory().getScope() == PwmSettingScope.SYSTEM )
+            {
+                final String msg = "attempt to read SYSTEM scope setting '" + setting.getKey() + "' via domain scope";
+                LOGGER.warn( () -> msg );
+            }
+        }
+        */
+
+
         if ( StringUtil.isEmpty( profileID ) )
         {
             if ( setting.getCategory().hasProfiles() )
@@ -157,6 +183,7 @@ public class SettingReader
             }
         }
 
-        return storedConfiguration.readSetting( setting, profileID );
+        final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID );
+        return StoredConfigurationUtil.getValueOrDefault( storedConfiguration, key );
     }
 }

+ 164 - 57
server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java

@@ -25,12 +25,18 @@ import com.novell.ldapchai.ChaiPasswordRule;
 import com.novell.ldapchai.util.StringHelper;
 import lombok.Builder;
 import lombok.Value;
+import password.pwm.AppProperty;
+import password.pwm.PwmConstants;
+import password.pwm.config.DomainConfig;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.SettingReader;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.password.PasswordRuleReaderHelper;
@@ -46,6 +52,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
 
@@ -58,15 +65,41 @@ public class PwmPasswordPolicy implements Profile, Serializable
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmPasswordPolicy.class );
 
-    private static final PwmPasswordPolicy DEFAULT_POLICY;
+    private static final PwmPasswordPolicy DEFAULT_POLICY = makeDefaultPolicy();
+
+    private final transient Supplier<List<HealthRecord>> healthChecker = new LazySupplier<>( () -> doHealthChecks( this ) );
+    private final transient ChaiPasswordPolicy chaiPasswordPolicy;
 
     private final Map<String, String> policyMap;
+    private final PolicyMetaData policyMetaData;
 
-    private final transient ChaiPasswordPolicy chaiPasswordPolicy;
+    private PwmPasswordPolicy(
+            final Map<String, String> policyMap,
+            final ChaiPasswordPolicy chaiPasswordPolicy,
+            final PolicyMetaData policyMetaData
+    )
+    {
+        final Map<String, String> effectivePolicyMap = new HashMap<>();
+        if ( policyMap != null )
+        {
+            effectivePolicyMap.putAll( policyMap );
+        }
+        if ( chaiPasswordPolicy != null )
+        {
+            if ( Boolean.parseBoolean( chaiPasswordPolicy.getValue( ChaiPasswordRule.ADComplexity ) ) )
+            {
+                effectivePolicyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2003.toString() );
+            }
+            else if ( Boolean.parseBoolean( chaiPasswordPolicy.getValue( ChaiPasswordRule.ADComplexity2008 ) ) )
+            {
+                effectivePolicyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2008.toString() );
+            }
+        }
 
-    private String profileID;
-    private List<UserPermission> userPermissions;
-    private String ruleText;
+        this.chaiPasswordPolicy = chaiPasswordPolicy;
+        this.policyMetaData = policyMetaData == null ? PolicyMetaData.builder().build() : policyMetaData;
+        this.policyMap = Collections.unmodifiableMap( effectivePolicyMap );
+    }
 
     public static PwmPasswordPolicy createPwmPasswordPolicy( final Map<String, String> policyMap )
     {
@@ -90,10 +123,90 @@ public class PwmPasswordPolicy implements Profile, Serializable
         return new PwmPasswordPolicy( policyMap, chaiPasswordPolicy, policyMetaData );
     }
 
+    public static PwmPasswordPolicy createPwmPasswordPolicy(
+            final DomainConfig domainConfig,
+            final String profileID,
+            final Locale locale
+    )
+    {
+        final SettingReader settingReader = new SettingReader( domainConfig.getStoredConfiguration(), domainConfig.getDomainID(), profileID );
+        final Map<String, String> passwordPolicySettings = new LinkedHashMap<>();
+        for ( final PwmPasswordRule rule : PwmPasswordRule.values() )
+        {
+            if ( rule.getPwmSetting() != null || rule.getAppProperty() != null )
+            {
+                final String value;
+                final PwmSetting pwmSetting = rule.getPwmSetting();
+                switch ( rule )
+                {
+                    case DisallowedAttributes:
+                    case DisallowedValues:
+                    case CharGroupsValues:
+                        value = StringHelper.stringCollectionToString(
+                                settingReader.readSettingAsStringArray( pwmSetting ), "\n" );
+                        break;
+                    case RegExMatch:
+                    case RegExNoMatch:
+                        value = StringHelper.stringCollectionToString(
+                                settingReader.readSettingAsStringArray( pwmSetting ), ";;;" );
+                        break;
+                    case ChangeMessage:
+                        value = settingReader.readSettingAsLocalizedString( pwmSetting, locale );
+                        break;
+                    case ADComplexityLevel:
+                        value = settingReader.readSettingAsEnum( pwmSetting, ADPolicyComplexity.class ).toString();
+                        break;
+                    case AllowMacroInRegExSetting:
+                        value = domainConfig.readAppProperty( AppProperty.ALLOW_MACRO_IN_REGEX_SETTING );
+                        break;
+                    default:
+                        switch ( rule.getRuleType() )
+                        {
+                            case MAX:
+                            case MIN:
+                            case NUMERIC:
+                                value = String.valueOf( settingReader.readSettingAsLong( pwmSetting ) );
+                                break;
+
+                            case BOOLEAN:
+                                value = String.valueOf( settingReader.readSettingAsBoolean( pwmSetting ) );
+                                break;
+
+                            default:
+                                value = settingReader.readSettingAsString( pwmSetting );
+                                break;
+                        }
+                        break;
+                }
+                passwordPolicySettings.put( rule.getKey(), value );
+            }
+        }
+
+        // set case sensitivity
+        final String caseSensitivitySetting = domainConfig.getAppConfig().readSettingAsString( PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY );
+        if ( !"read".equals( caseSensitivitySetting ) )
+        {
+            passwordPolicySettings.put( PwmPasswordRule.CaseSensitive.getKey(), caseSensitivitySetting );
+        }
+
+        // set pwm-specific values
+        final List<UserPermission> queryMatch = settingReader.readSettingAsUserPermission( PwmSetting.PASSWORD_POLICY_QUERY_MATCH );
+        final String ruleText = settingReader.readSettingAsLocalizedString( PwmSetting.PASSWORD_POLICY_RULE_TEXT, locale );
+
+        final PwmPasswordPolicy.PolicyMetaData policyMetaData = PwmPasswordPolicy.PolicyMetaData.builder()
+                .profileID( profileID )
+                .userPermissions( queryMatch )
+                .ruleText( ruleText )
+                .build();
+
+        return PwmPasswordPolicy.createPwmPasswordPolicy( passwordPolicySettings, null, policyMetaData );
+
+    }
+
     @Override
     public String getIdentifier( )
     {
-        return profileID;
+        return policyMetaData.getProfileID();
     }
 
     @Override
@@ -102,7 +215,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
         return getIdentifier();
     }
 
-    static
+    private static PwmPasswordPolicy makeDefaultPolicy()
     {
         PwmPasswordPolicy newDefaultPolicy = null;
         try
@@ -118,7 +231,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
         {
             LOGGER.fatal( () -> "error initializing PwmPasswordPolicy class: " + t.getMessage(), t );
         }
-        DEFAULT_POLICY = newDefaultPolicy;
+        return newDefaultPolicy;
     }
 
     public static PwmPasswordPolicy defaultPolicy( )
@@ -127,38 +240,6 @@ public class PwmPasswordPolicy implements Profile, Serializable
     }
 
 
-    private PwmPasswordPolicy(
-            final Map<String, String> policyMap,
-            final ChaiPasswordPolicy chaiPasswordPolicy,
-            final PolicyMetaData policyMetaData
-    )
-    {
-        final Map<String, String> effectivePolicyMap = new HashMap<>();
-        if ( policyMap != null )
-        {
-            effectivePolicyMap.putAll( policyMap );
-        }
-        if ( chaiPasswordPolicy != null )
-        {
-            if ( Boolean.parseBoolean( chaiPasswordPolicy.getValue( ChaiPasswordRule.ADComplexity ) ) )
-            {
-                effectivePolicyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2003.toString() );
-            }
-            else if ( Boolean.parseBoolean( chaiPasswordPolicy.getValue( ChaiPasswordRule.ADComplexity2008 ) ) )
-            {
-                effectivePolicyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2008.toString() );
-            }
-        }
-        this.chaiPasswordPolicy = chaiPasswordPolicy;
-        if ( policyMetaData != null )
-        {
-            this.ruleText = policyMetaData.getRuleText();
-            this.userPermissions = policyMetaData.getUserPermissions();
-            this.profileID = policyMetaData.getProfileID();
-        }
-
-        this.policyMap = Collections.unmodifiableMap( effectivePolicyMap );
-    }
 
     @Override
     public String toString( )
@@ -185,12 +266,12 @@ public class PwmPasswordPolicy implements Profile, Serializable
 
     public List<UserPermission> getUserPermissions( )
     {
-        return userPermissions;
+        return policyMetaData.getUserPermissions();
     }
 
     public String getRuleText( )
     {
-        return ruleText;
+        return policyMetaData.getRuleText();
     }
 
     public PwmPasswordPolicy merge( final PwmPasswordPolicy otherPolicy )
@@ -215,11 +296,11 @@ public class PwmPasswordPolicy implements Profile, Serializable
                     case RegExMatch:
                     case RegExNoMatch:
                     case CharGroupsValues:
-                        final String seperator = ( rule == PwmPasswordRule.RegExMatch || rule == PwmPasswordRule.RegExNoMatch ) ? ";;;" : "\n";
+                        final String separator = ( rule == PwmPasswordRule.RegExMatch || rule == PwmPasswordRule.RegExNoMatch ) ? ";;;" : "\n";
                         final Set<String> combinedSet = new HashSet<>();
-                        combinedSet.addAll( StringHelper.tokenizeString( this.policyMap.get( rule.getKey() ), seperator ) );
-                        combinedSet.addAll( StringHelper.tokenizeString( otherPolicy.policyMap.get( rule.getKey() ), seperator ) );
-                        newPasswordPolicies.put( ruleKey, StringHelper.stringCollectionToString( combinedSet, seperator ) );
+                        combinedSet.addAll( StringHelper.tokenizeString( this.policyMap.get( rule.getKey() ), separator ) );
+                        combinedSet.addAll( StringHelper.tokenizeString( otherPolicy.policyMap.get( rule.getKey() ), separator ) );
+                        newPasswordPolicies.put( ruleKey, StringUtil.collectionToString( combinedSet, separator ) );
                         break;
 
                     case ChangeMessage:
@@ -287,10 +368,13 @@ public class PwmPasswordPolicy implements Profile, Serializable
         }
 
         final ChaiPasswordPolicy backingPolicy = this.chaiPasswordPolicy != null ? chaiPasswordPolicy : otherPolicy.chaiPasswordPolicy;
-        final PwmPasswordPolicy returnPolicy = createPwmPasswordPolicy( newPasswordPolicies, backingPolicy );
-        final String newRuleText = ( ruleText != null && !ruleText.isEmpty() ) ? ruleText : otherPolicy.ruleText;
-        returnPolicy.ruleText = ( newRuleText );
-        return returnPolicy;
+        final PolicyMetaData metaData = getPolicyMetaData().merge( otherPolicy.getPolicyMetaData() );
+        return new PwmPasswordPolicy( newPasswordPolicies, backingPolicy, metaData );
+    }
+
+    private PolicyMetaData getPolicyMetaData()
+    {
+        return policyMetaData;
     }
 
     private static String mergeADComplexityLevel( final String value1, final String value2 )
@@ -351,7 +435,16 @@ public class PwmPasswordPolicy implements Profile, Serializable
 
     public List<HealthRecord> health( final Locale locale )
     {
-        final PasswordRuleReaderHelper ruleHelper = this.getRuleHelper();
+        return healthChecker.get();
+    }
+
+
+    private static List<HealthRecord> doHealthChecks( final PwmPasswordPolicy pwmPasswordPolicy )
+    {
+        final Locale locale = PwmConstants.DEFAULT_LOCALE;
+        final PolicyMetaData policyMetaData = pwmPasswordPolicy.getPolicyMetaData();
+
+        final PasswordRuleReaderHelper ruleHelper = pwmPasswordPolicy.getRuleHelper();
         final List<HealthRecord> returnList = new ArrayList<>();
         final Map<PwmPasswordRule, PwmPasswordRule> rulePairs = new LinkedHashMap<>();
         rulePairs.put( PwmPasswordRule.MinimumLength, PwmPasswordRule.MaximumLength );
@@ -375,7 +468,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
                 final String detailMsg = minRule.getLabel( locale, null ) + " (" + minValue + ")"
                         + " > "
                         + maxRule.getLabel( locale, null ) + " (" + maxValue + ")";
-                returnList.add( HealthRecord.forMessage( HealthMessage.Config_PasswordPolicyProblem, profileID, detailMsg ) );
+                returnList.add( HealthRecord.forMessage( HealthMessage.Config_PasswordPolicyProblem, policyMetaData.getProfileID(), detailMsg ) );
             }
         }
 
@@ -389,19 +482,33 @@ public class PwmPasswordPolicy implements Profile, Serializable
                 final String detailMsg = PwmPasswordRule.CharGroupsValues.getLabel( locale, null ) + " (" + minValue + ")"
                         + " > "
                         + PwmPasswordRule.CharGroupsMinMatch.getLabel( locale, null ) + " (" + maxValue + ")";
-                returnList.add( HealthRecord.forMessage( HealthMessage.Config_PasswordPolicyProblem, profileID, detailMsg ) );
+                returnList.add( HealthRecord.forMessage( HealthMessage.Config_PasswordPolicyProblem, policyMetaData.getProfileID(), detailMsg ) );
             }
         }
 
         return Collections.unmodifiableList( returnList );
     }
 
+
+
     @Value
     @Builder
-    public static class PolicyMetaData
+    public static class PolicyMetaData implements Serializable
     {
-        private String profileID;
-        private List<UserPermission> userPermissions;
-        private String ruleText;
+        private final String profileID;
+
+        @Builder.Default
+        private final List<UserPermission> userPermissions = Collections.emptyList();
+
+        private final String ruleText;
+
+        private PolicyMetaData merge( final PolicyMetaData otherPolicy )
+        {
+            return PolicyMetaData.builder()
+                    .ruleText( StringUtil.isEmpty( ruleText ) ? otherPolicy.ruleText : ruleText )
+                    .userPermissions( JavaHelper.isEmpty( userPermissions ) ? otherPolicy.userPermissions : userPermissions )
+                    .profileID( StringUtil.isEmpty( profileID ) ? otherPolicy.profileID : profileID )
+                    .build();
+        }
     }
 }

+ 1 - 21
server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java

@@ -28,8 +28,6 @@ import password.pwm.i18n.Message;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -490,24 +488,6 @@ public enum PwmPasswordRule
         return positiveBooleanMerge;
     }
 
-    public static PwmPasswordRule forKey( final String key )
-    {
-        if ( key == null )
-        {
-            return null;
-        }
-
-        for ( final PwmPasswordRule rule : values() )
-        {
-            if ( key.equals( rule.getKey() ) )
-            {
-                return rule;
-            }
-        }
-
-        return null;
-    }
-
     public String getLabel( final Locale locale, final DomainConfig config )
     {
         final String key = "Rule_" + this.toString();
@@ -528,6 +508,6 @@ public enum PwmPasswordRule
         {
             sortedMap.put( rule.getLabel( locale, config ), rule );
         }
-        return Collections.unmodifiableList( new ArrayList<>( sortedMap.values() ) );
+        return List.copyOf( sortedMap.values() );
     }
 }

+ 6 - 1
server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java

@@ -99,11 +99,16 @@ public class StoredConfigItemKey implements Serializable, Comparable<StoredConfi
         return profileID;
     }
 
-    static StoredConfigItemKey fromSetting( final PwmSetting pwmSetting, final String profileID )
+    public static StoredConfigItemKey fromSetting( final PwmSetting pwmSetting, final String profileID )
     {
         return new StoredConfigItemKey( RecordType.SETTING, null, pwmSetting.getKey(), profileID );
     }
 
+    public static StoredConfigItemKey fromSetting( final PwmSetting pwmSetting, final String profileID, final String domainID )
+    {
+        return new StoredConfigItemKey( RecordType.SETTING, domainID, pwmSetting.getKey(), profileID );
+    }
+
     static StoredConfigItemKey fromLocaleBundle( final PwmLocaleBundle localeBundle, final String key )
     {
         return new StoredConfigItemKey( RecordType.LOCALE_BUNDLE, null, localeBundle.getKey(), key );

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

@@ -80,7 +80,7 @@ public final class ValueTypeConverter
         }
         if ( ( !( value instanceof StringValue ) ) && ( !( value instanceof BooleanValue ) ) )
         {
-            throw new IllegalArgumentException( "setting value is not readable as string" );
+            throw new IllegalArgumentException( "setting value is type '" + value.getClass().getSimpleName() + "' not readable as string" );
         }
         final Object nativeObject = value.toNativeObject();
         if ( nativeObject == null )

+ 2 - 3
server/src/main/java/password/pwm/svc/stats/Statistic.java

@@ -24,6 +24,7 @@ import password.pwm.PwmDomain;
 import password.pwm.config.PwmSetting;
 import password.pwm.i18n.Admin;
 import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.TimeDuration;
 
 import java.util.Arrays;
@@ -201,8 +202,6 @@ public enum Statistic
 
     public static Optional<Statistic> forKey( final String key )
     {
-        return Arrays.stream( values() )
-                .filter( loopValue -> loopValue.getKey().equals( key ) )
-                .findFirst();
+        return JavaHelper.readEnumFromPredicate( Statistic.class,  loopValue -> loopValue.getKey().equals( key ) );
     }
 }

+ 16 - 0
server/src/main/java/password/pwm/util/java/JavaHelper.java

@@ -181,6 +181,22 @@ public class JavaHelper
         return readEnumFromString( enumClass, input ).orElse( defaultValue );
     }
 
+
+    public static <E extends Enum<E>> Optional<E> readEnumFromPredicate( final Class<E> enumClass, final Predicate<E> match )
+    {
+        if ( match == null )
+        {
+            return Optional.empty();
+        }
+
+        if ( enumClass == null || !enumClass.isEnum() )
+        {
+            return Optional.empty();
+        }
+
+        return EnumSet.allOf( enumClass ).stream().filter( match ).findFirst();
+    }
+
     public static <E extends Enum<E>> Optional<E> readEnumFromString( final Class<E> enumClass, final String input )
     {
         if ( StringUtil.isEmpty( input ) )