Kaynağa Gözat

add domainID to StoredConfigKey

Jason Rivard 4 yıl önce
ebeveyn
işleme
1ad04b0d20
58 değiştirilmiş dosya ile 645 ekleme ve 502 silme
  1. 2 2
      server/src/main/java/password/pwm/bean/DomainID.java
  2. 2 0
      server/src/main/java/password/pwm/bean/SessionLabel.java
  3. 5 11
      server/src/main/java/password/pwm/bean/UserIdentity.java
  4. 1 1
      server/src/main/java/password/pwm/config/AppConfig.java
  5. 13 25
      server/src/main/java/password/pwm/config/DomainConfig.java
  6. 3 1
      server/src/main/java/password/pwm/config/PwmSetting.java
  7. 1 0
      server/src/main/java/password/pwm/config/PwmSettingSyntax.java
  8. 12 14
      server/src/main/java/password/pwm/config/SettingReader.java
  9. 3 3
      server/src/main/java/password/pwm/config/profile/AbstractProfile.java
  10. 5 4
      server/src/main/java/password/pwm/config/profile/AccountInformationProfile.java
  11. 5 4
      server/src/main/java/password/pwm/config/profile/ActivateUserProfile.java
  12. 5 4
      server/src/main/java/password/pwm/config/profile/ChangePasswordProfile.java
  13. 5 4
      server/src/main/java/password/pwm/config/profile/DeleteAccountProfile.java
  14. 5 4
      server/src/main/java/password/pwm/config/profile/EmailServerProfile.java
  15. 5 4
      server/src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java
  16. 5 4
      server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java
  17. 5 4
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  18. 6 5
      server/src/main/java/password/pwm/config/profile/NewUserProfile.java
  19. 5 4
      server/src/main/java/password/pwm/config/profile/PeopleSearchProfile.java
  20. 2 1
      server/src/main/java/password/pwm/config/profile/Profile.java
  21. 2 2
      server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java
  22. 5 4
      server/src/main/java/password/pwm/config/profile/SetupOtpProfile.java
  23. 5 4
      server/src/main/java/password/pwm/config/profile/UpdateProfileProfile.java
  24. 47 9
      server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
  25. 11 7
      server/src/main/java/password/pwm/config/stored/StoredConfigKey.java
  26. 1 32
      server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  27. 53 5
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  28. 1 1
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  29. 7 1
      server/src/main/java/password/pwm/http/PwmSession.java
  30. 33 26
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  31. 1 1
      server/src/main/java/password/pwm/http/filter/SessionFilter.java
  32. 7 4
      server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java
  33. 4 1
      server/src/main/java/password/pwm/http/servlet/FullPageHealthServlet.java
  34. 29 1
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  35. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java
  36. 2 2
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java
  37. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java
  38. 1 1
      server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java
  39. 1 1
      server/src/main/java/password/pwm/http/tag/value/PwmValue.java
  40. 4 4
      server/src/main/java/password/pwm/svc/email/EmailServerUtil.java
  41. 1 1
      server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java
  42. 1 3
      server/src/main/java/password/pwm/util/Validator.java
  43. 2 2
      server/src/main/java/password/pwm/util/i18n/LocaleHelper.java
  44. 2 0
      server/src/main/java/password/pwm/util/logging/PwmLogEvent.java
  45. 1 1
      server/src/main/java/password/pwm/util/password/RandomPasswordGenerator.java
  46. 2 2
      server/src/main/java/password/pwm/ws/server/RestServlet.java
  47. 6 2
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  48. 1 0
      server/src/main/resources/password/pwm/i18n/Config.properties
  49. 2 1
      server/src/test/java/password/pwm/config/stored/ConfigurationCleanerTest.java
  50. 1 1
      server/src/test/java/password/pwm/config/stored/StoredConfigurationModifierTest.java
  51. 18 18
      server/src/test/java/password/pwm/http/filter/RequestInitializationFilterTest.java
  52. 2 3
      server/src/test/java/password/pwm/i18n/NonLocalizedKeyTest.java
  53. 1 0
      webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp
  54. 2 2
      webapp/src/main/webapp/public/localeselect.jsp
  55. 6 6
      webapp/src/main/webapp/public/reference/localeinfo.jsp
  56. 283 0
      webapp/src/main/webapp/public/resources/js/configeditor-settings-stringarray.js
  57. 0 253
      webapp/src/main/webapp/public/resources/js/configeditor-settings.js
  58. 3 0
      webapp/src/main/webapp/public/resources/js/configeditor.js

+ 2 - 2
server/src/main/java/password/pwm/bean/DomainID.java

@@ -88,8 +88,8 @@ public class DomainID implements Comparable<DomainID>, Serializable
         {
             return false;
         }
-        final DomainID domainID1 = ( DomainID ) o;
-        return Objects.equals( domainID, domainID1.domainID );
+        final DomainID otherDomainID = ( DomainID ) o;
+        return Objects.equals( domainID, otherDomainID.domainID );
     }
 
     @Override

+ 2 - 0
server/src/main/java/password/pwm/bean/SessionLabel.java

@@ -47,4 +47,6 @@ public class SessionLabel implements Serializable
     private final String username;
     private final String sourceAddress;
     private final String sourceHostname;
+    private final String profile;
+    private final String domain;
 }

+ 5 - 11
server/src/main/java/password/pwm/bean/UserIdentity.java

@@ -25,7 +25,6 @@ import com.novell.ldapchai.exception.ChaiException;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.jetbrains.annotations.NotNull;
 import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
 import password.pwm.config.AppConfig;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.error.ErrorInformation;
@@ -64,12 +63,6 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
                     UserIdentity::getDomainID,
                     Comparator.nullsLast( Comparator.naturalOrder() ) );
 
-    public static final String XML_DOMAIN = "domain";
-    public static final String XML_LDAP_PROFILE = "ldapProfile";
-    public static final String XML_DISTINGUISHED_NAME = "distinguishedName";
-    public static final String XML_USER_IDENTITY = "userIdentity";
-
-
     private transient String obfuscatedValue;
     private transient boolean canonical;
 
@@ -222,10 +215,11 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         // old style
         final StringTokenizer st = new StringTokenizer( key, DELIM_SEPARATOR );
 
-        DomainID domainID = PwmConstants.DOMAIN_ID_PLACEHOLDER;
+        DomainID domainID = null;
         if ( st.countTokens() < 2 )
         {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "not enough tokens while parsing delimited identity key" ) );
+            final String msg = "not enough tokens while parsing delimited identity key";
+            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) );
         }
         else if ( st.countTokens() > 2 )
         {
@@ -237,8 +231,8 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
             }
             catch ( final Exception e )
             {
-                final String fDomainStr = domainStr;
-                LOGGER.trace( () -> "error decoding DomainID '" + fDomainStr + "' from delimited UserIdentity: " + e.getMessage() );
+                final String msg = "error decoding DomainID '" + domainStr + "' from delimited UserIdentity: " + e.getMessage();
+                throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) );
             }
         }
 

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

@@ -281,7 +281,7 @@ public class AppConfig
 
     public Map<String, EmailServerProfile> getEmailServerProfiles( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.EmailServers );
+        return settingReader.getProfileMap( ProfileDefinition.EmailServers, DomainID.systemId() );
     }
 
     public CertificateMatchingMode readCertificateMatchingMode()

+ 13 - 25
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -190,7 +190,7 @@ public class DomainConfig
 
     public List<String> getChallengeProfileIDs( )
     {
-        return StoredConfigurationUtil.profilesForSetting( PwmSetting.CHALLENGE_PROFILE_LIST, storedConfiguration );
+        return StoredConfigurationUtil.profilesForSetting( this.getDomainID(), PwmSetting.CHALLENGE_PROFILE_LIST, storedConfiguration );
     }
 
     public ChallengeProfile getChallengeProfile( final String profile, final Locale locale )
@@ -216,7 +216,7 @@ public class DomainConfig
 
     public List<String> getPasswordProfileIDs( )
     {
-        return StoredConfigurationUtil.profilesForSetting( PwmSetting.PASSWORD_PROFILE_LIST, storedConfiguration );
+        return StoredConfigurationUtil.profilesForSetting( this.getDomainID(), PwmSetting.PASSWORD_PROFILE_LIST, storedConfiguration );
     }
 
 
@@ -282,18 +282,6 @@ public class DomainConfig
         return getLdapProfiles().values().iterator().next();
     }
 
-
-    public List<Locale> getKnownLocales( )
-    {
-        return getAppConfig().getKnownLocales();
-    }
-
-    public Map<Locale, String> getKnownLocaleFlagMap( )
-    {
-        return getAppConfig().getKnownLocaleFlagMap();
-    }
-
-
     public Optional<TokenStorageMethod> getTokenStorageMethod( )
     {
         return JavaHelper.readEnumFromString( TokenStorageMethod.class, readSettingAsString( PwmSetting.TOKEN_STORAGEMETHOD ) );
@@ -318,7 +306,7 @@ public class DomainConfig
     {
         private final Supplier<Map<String, LdapProfile>> ldapProfilesSupplier = new LazySupplier<>( () ->
         {
-            final Map<String, LdapProfile> sourceMap = settingReader.getProfileMap( ProfileDefinition.LdapProfile );
+            final Map<String, LdapProfile> sourceMap = settingReader.getProfileMap( ProfileDefinition.LdapProfile, getDomainID() );
 
             return Collections.unmodifiableMap(
                     sourceMap.entrySet()
@@ -378,47 +366,47 @@ public class DomainConfig
     /* generic profile stuff */
     public Map<String, NewUserProfile> getNewUserProfiles( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.NewUser );
+        return this.getProfileMap( ProfileDefinition.NewUser );
     }
 
     public Map<String, ActivateUserProfile> getUserActivationProfiles( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.ActivateUser );
+        return this.getProfileMap( ProfileDefinition.ActivateUser );
     }
 
     public Map<String, HelpdeskProfile> getHelpdeskProfiles( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.Helpdesk );
+        return this.getProfileMap( ProfileDefinition.Helpdesk );
     }
 
     public Map<String, PeopleSearchProfile> getPeopleSearchProfiles( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.PeopleSearch );
+        return this.getProfileMap( ProfileDefinition.PeopleSearch );
     }
 
     public Map<String, SetupOtpProfile> getSetupOTPProfiles( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.SetupOTPProfile );
+        return this.getProfileMap( ProfileDefinition.SetupOTPProfile );
     }
 
     public Map<String, UpdateProfileProfile> getUpdateAttributesProfile( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.UpdateAttributes );
+        return this.getProfileMap( ProfileDefinition.UpdateAttributes );
     }
 
     public Map<String, ChangePasswordProfile> getChangePasswordProfile( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.ChangePassword );
+        return this.getProfileMap( ProfileDefinition.ChangePassword );
     }
 
     public Map<String, ForgottenPasswordProfile> getForgottenPasswordProfiles( )
     {
-        return settingReader.getProfileMap( ProfileDefinition.ForgottenPassword );
+        return this.getProfileMap( ProfileDefinition.ForgottenPassword );
     }
 
     public <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition )
     {
-        return settingReader.getProfileMap( profileDefinition );
+        return settingReader.getProfileMap( profileDefinition, getDomainID()  );
     }
 
     public StoredConfiguration getStoredConfiguration( )
@@ -431,7 +419,7 @@ public class DomainConfig
         if ( readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC ) )
         {
             final String profileID = readSettingAsString( PwmSetting.PEOPLE_SEARCH_PUBLIC_PROFILE );
-            final Map<String, PeopleSearchProfile> profiles = settingReader.getProfileMap( ProfileDefinition.PeopleSearchPublic );
+            final Map<String, PeopleSearchProfile> profiles = settingReader.getProfileMap( ProfileDefinition.PeopleSearchPublic, getDomainID() );
             return Optional.ofNullable( profiles.get( profileID ) );
         }
         return Optional.empty();

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

@@ -58,7 +58,7 @@ public enum PwmSetting
 
     // domains
     DOMAIN_LIST(
-            "domain.list", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.DOMAINS ),
+            "domain.list", PwmSettingSyntax.DOMAINS, PwmSettingCategory.DOMAINS ),
 
     // application settings
     APP_PROPERTY_OVERRIDES(
@@ -83,6 +83,8 @@ public enum PwmSetting
             "idleTimeoutSeconds", PwmSettingSyntax.DURATION, PwmSettingCategory.GENERAL ),
     HIDE_CONFIGURATION_HEALTH_WARNINGS(
             "display.hideConfigHealthWarnings", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.GENERAL ),
+
+
     KNOWN_LOCALES(
             "knownLocales", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.LOCALIZATION ),
     LOCALE_COOKIE_MAX_AGE(

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

@@ -71,6 +71,7 @@ public enum PwmSettingSyntax
     OPTIONLIST( OptionListValue.factory() ),
     FILE( FileValue.factory() ),
     PROFILE( StringArrayValue.factory() ),
+    DOMAINS( StringArrayValue.factory() ),
     VERIFICATION_METHOD( VerificationMethodValue.factory() ),
     PRIVATE_KEY( PrivateKeyValue.factory() ),
     NAMED_SECRET( NamedSecretValue.factory() ),

+ 12 - 14
server/src/main/java/password/pwm/config/SettingReader.java

@@ -194,9 +194,9 @@ public class SettingReader
     }
 
 
-    public <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition )
+    public <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition, final DomainID domainID )
     {
-        return profileReader.getProfileMap( profileDefinition );
+        return profileReader.getProfileMap( profileDefinition, domainID );
     }
 
 
@@ -204,12 +204,12 @@ public class SettingReader
     {
         private final Map<ProfileDefinition, Map> profileCache = new LinkedHashMap<>();
 
-        public <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition )
+        private <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition, final DomainID domainID )
         {
             return profileCache.computeIfAbsent( profileDefinition, ( p ) ->
             {
                 final Map<String, T> returnMap = new LinkedHashMap<>();
-                final Map<String, Profile> profileMap = profileMap( profileDefinition );
+                final Map<String, Profile> profileMap = profileMap( profileDefinition, domainID );
                 for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
                 {
                     returnMap.put( entry.getKey(), ( T ) entry.getValue() );
@@ -218,7 +218,7 @@ public class SettingReader
             } );
         }
 
-        private Map<String, Profile> profileMap( final ProfileDefinition profileDefinition )
+        private Map<String, Profile> profileMap( final ProfileDefinition profileDefinition, final DomainID domainID )
         {
             if ( profileDefinition.getProfileFactoryClass().isEmpty() )
             {
@@ -228,11 +228,11 @@ public class SettingReader
             return profileIDsForCategory( profileDefinition.getCategory() ).stream()
                     .collect( Collectors.toUnmodifiableMap(
                         profileID -> profileID,
-                        profileID -> newProfileForID( profileDefinition, profileID )
+                        profileID -> newProfileForID( profileDefinition, domainID, profileID )
                     ) );
         }
 
-        private Profile newProfileForID( final ProfileDefinition profileDefinition, final String profileID )
+        private Profile newProfileForID( final ProfileDefinition profileDefinition, final DomainID domainID, final String profileID )
         {
             Objects.requireNonNull( profileDefinition );
             Objects.requireNonNull( profileID );
@@ -245,7 +245,7 @@ public class SettingReader
                 try
                 {
                     profileFactory = optionalProfileFactoryClass.get().getDeclaredConstructor().newInstance();
-                    return profileFactory.makeFromStoredConfiguration( storedConfiguration, profileID );
+                    return profileFactory.makeFromStoredConfiguration( storedConfiguration, domainID, profileID );
                 }
                 catch ( final InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e )
                 {
@@ -269,20 +269,18 @@ public class SettingReader
         {
             if ( setting.getCategory().getScope() == PwmSettingScope.DOMAIN )
             {
-                final String msg = "attempt to read DOMAIN scope setting '" + setting.toMenuLocationDebug( profileID, null ) + "' via system scope";
+                final String msg = "attempt to read system scope setting '" + setting.toMenuLocationDebug( profileID, null ) + "' as system scope";
                 final PwmUnrecoverableException pwmUnrecoverableException = PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, msg );
-                LOGGER.error( () -> pwmUnrecoverableException.getErrorInformation().toDebugStr(), pwmUnrecoverableException );
-                //throw new IllegalStateException( msg );
+                throw new IllegalStateException( msg, pwmUnrecoverableException );
             }
         }
         else
         {
             if ( setting.getCategory().getScope() == PwmSettingScope.SYSTEM )
             {
-                final String msg = "attempt to read SYSTEM scope setting '" + setting.toMenuLocationDebug( profileID, null ) + "' via domain scope";
+                final String msg = "attempt to read system scope setting '" + setting.toMenuLocationDebug( profileID, null ) + "' as domain scope";
                 final PwmUnrecoverableException pwmUnrecoverableException = PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, msg );
-                LOGGER.error( () -> pwmUnrecoverableException.getErrorInformation().toDebugStr(), pwmUnrecoverableException );
-                //throw new IllegalStateException( msg );
+                throw new IllegalStateException( msg, pwmUnrecoverableException );
             }
         }
 

+ 3 - 3
server/src/main/java/password/pwm/config/profile/AbstractProfile.java

@@ -20,7 +20,7 @@
 
 package password.pwm.config.profile;
 
-import password.pwm.PwmConstants;
+import password.pwm.bean.DomainID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.SettingReader;
 import password.pwm.config.option.IdentityVerificationMethod;
@@ -45,11 +45,11 @@ public abstract class AbstractProfile implements Profile
     private final StoredConfiguration storedConfiguration;
     private final SettingReader settingReader;
 
-    AbstractProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    AbstractProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
         this.identifier = identifier;
         this.storedConfiguration = storedConfiguration;
-        this.settingReader = new SettingReader( storedConfiguration, identifier, PwmConstants.DOMAIN_ID_PLACEHOLDER );
+        this.settingReader = new SettingReader( storedConfiguration, identifier, domainID );
     }
 
     @Override

+ 5 - 4
server/src/main/java/password/pwm/config/profile/AccountInformationProfile.java

@@ -20,15 +20,16 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class AccountInformationProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.AccountInformation;
 
-    protected AccountInformationProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected AccountInformationProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -40,9 +41,9 @@ public class AccountInformationProfile extends AbstractProfile implements Profil
     public static class AccountInformationProfileFactory implements Profile.ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new AccountInformationProfile( identifier, storedConfiguration );
+            return new AccountInformationProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/ActivateUserProfile.java

@@ -20,15 +20,16 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class ActivateUserProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.ActivateUser;
 
-    protected ActivateUserProfile( final String identifier, final StoredConfiguration storedValueMap )
+    protected ActivateUserProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedValueMap )
     {
-        super( identifier, storedValueMap );
+        super( domainID, identifier, storedValueMap );
     }
 
     @Override
@@ -40,9 +41,9 @@ public class ActivateUserProfile extends AbstractProfile implements Profile
     public static class UserActivationProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new ActivateUserProfile( identifier, storedConfiguration );
+            return new ActivateUserProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/ChangePasswordProfile.java

@@ -20,15 +20,16 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class ChangePasswordProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.ChangePassword;
 
-    protected ChangePasswordProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected ChangePasswordProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -40,9 +41,9 @@ public class ChangePasswordProfile extends AbstractProfile implements Profile
     public static class ChangePasswordProfileFactory implements Profile.ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new ChangePasswordProfile( identifier, storedConfiguration );
+            return new ChangePasswordProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/DeleteAccountProfile.java

@@ -20,15 +20,16 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class DeleteAccountProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.DeleteAccount;
 
-    protected DeleteAccountProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected DeleteAccountProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -40,9 +41,9 @@ public class DeleteAccountProfile extends AbstractProfile implements Profile
     public static class DeleteAccountProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new DeleteAccountProfile( identifier, storedConfiguration );
+            return new DeleteAccountProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/EmailServerProfile.java

@@ -20,6 +20,7 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 
 import java.util.Locale;
@@ -29,9 +30,9 @@ public class EmailServerProfile extends AbstractProfile
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.EmailServers;
 
-    protected EmailServerProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected EmailServerProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -49,9 +50,9 @@ public class EmailServerProfile extends AbstractProfile
     public static class EmailServerProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new EmailServerProfile( identifier, storedConfiguration );
+            return new EmailServerProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java

@@ -20,6 +20,7 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
@@ -35,9 +36,9 @@ public class ForgottenPasswordProfile extends AbstractProfile
     private Set<IdentityVerificationMethod> requiredRecoveryVerificationMethods;
     private Set<IdentityVerificationMethod> optionalRecoveryVerificationMethods;
 
-    public ForgottenPasswordProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    public ForgottenPasswordProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -79,9 +80,9 @@ public class ForgottenPasswordProfile extends AbstractProfile
     public static class ForgottenPasswordProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new ForgottenPasswordProfile( identifier, storedConfiguration );
+            return new ForgottenPasswordProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java

@@ -20,6 +20,7 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
@@ -34,9 +35,9 @@ public class HelpdeskProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.Helpdesk;
 
-    protected HelpdeskProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected HelpdeskProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -61,9 +62,9 @@ public class HelpdeskProfile extends AbstractProfile implements Profile
     public static class HelpdeskProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new HelpdeskProfile( identifier, storedConfiguration );
+            return new HelpdeskProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -26,6 +26,7 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmDomain;
+import password.pwm.bean.DomainID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfiguration;
@@ -53,9 +54,9 @@ public class LdapProfile extends AbstractProfile implements Profile
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.LdapProfile;
 
-    protected LdapProfile( final String identifier, final StoredConfiguration storedValueMap )
+    protected LdapProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedValueMap )
     {
-        super( identifier, storedValueMap );
+        super( domainID, identifier, storedValueMap );
     }
 
     public Map<String, String> getSelectableContexts(
@@ -214,9 +215,9 @@ public class LdapProfile extends AbstractProfile implements Profile
     public static class LdapProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new LdapProfile( identifier, storedConfiguration );
+            return new LdapProfile( domainID, identifier, storedConfiguration );
         }
     }
 

+ 6 - 5
server/src/main/java/password/pwm/config/profile/NewUserProfile.java

@@ -24,8 +24,9 @@ import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
+import password.pwm.bean.DomainID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
@@ -52,9 +53,9 @@ public class NewUserProfile extends AbstractProfile implements Profile
     private Instant newUserPasswordPolicyCacheTime;
     private final Map<Locale, PwmPasswordPolicy> newUserPasswordPolicyCache = new HashMap<>();
 
-    protected NewUserProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected NewUserProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -170,9 +171,9 @@ public class NewUserProfile extends AbstractProfile implements Profile
     public static class NewUserProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new NewUserProfile( identifier, storedConfiguration );
+            return new NewUserProfile( domainID, identifier, storedConfiguration );
         }
     }
 

+ 5 - 4
server/src/main/java/password/pwm/config/profile/PeopleSearchProfile.java

@@ -20,6 +20,7 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class PeopleSearchProfile extends AbstractProfile
@@ -27,9 +28,9 @@ public class PeopleSearchProfile extends AbstractProfile
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.PeopleSearch;
 
-    protected PeopleSearchProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected PeopleSearchProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -41,9 +42,9 @@ public class PeopleSearchProfile extends AbstractProfile
     public static class PeopleSearchProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new PeopleSearchProfile( identifier, storedConfiguration );
+            return new PeopleSearchProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

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

@@ -20,6 +20,7 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.data.UserPermission;
 
@@ -38,6 +39,6 @@ public interface Profile
 
     interface ProfileFactory
     {
-        Profile makeFromStoredConfiguration( StoredConfiguration storedConfiguration, String identifier );
+        Profile makeFromStoredConfiguration( StoredConfiguration storedConfiguration, DomainID domainID, String identifier );
     }
 }

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

@@ -185,7 +185,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
         }
 
         // set case sensitivity
-        final String caseSensitivitySetting = domainConfig.getAppConfig().readSettingAsString( PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY );
+        final String caseSensitivitySetting = domainConfig.readSettingAsString( PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY );
         if ( !"read".equals( caseSensitivitySetting ) )
         {
             passwordPolicySettings.put( PwmPasswordRule.CaseSensitive.getKey(), caseSensitivitySetting );
@@ -209,7 +209,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
             final SettingReader settingReader
     )
     {
-        final List<Locale> knownLocales = domainConfig.getKnownLocales();
+        final List<Locale> knownLocales = domainConfig.getAppConfig().getKnownLocales();
         final String defaultLocaleValue = settingReader.readSettingAsLocalizedString( pwmSetting, PwmConstants.DEFAULT_LOCALE );
         final Map<Locale, String> returnMap = new HashMap<>();
         returnMap.put( PwmConstants.DEFAULT_LOCALE, defaultLocaleValue );

+ 5 - 4
server/src/main/java/password/pwm/config/profile/SetupOtpProfile.java

@@ -20,15 +20,16 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class SetupOtpProfile extends AbstractProfile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.SetupOTPProfile;
 
-    protected SetupOtpProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected SetupOtpProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -40,9 +41,9 @@ public class SetupOtpProfile extends AbstractProfile
     public static class SetupOtpProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID,  final String identifier )
         {
-            return new SetupOtpProfile( identifier, storedConfiguration );
+            return new SetupOtpProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 5 - 4
server/src/main/java/password/pwm/config/profile/UpdateProfileProfile.java

@@ -20,6 +20,7 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.bean.DomainID;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfiguration;
@@ -30,9 +31,9 @@ public class UpdateProfileProfile extends AbstractProfile implements Profile
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.UpdateAttributes;
 
-    protected UpdateProfileProfile( final String identifier, final StoredConfiguration storedConfiguration )
+    protected UpdateProfileProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
     {
-        super( identifier, storedConfiguration );
+        super( domainID, identifier, storedConfiguration );
     }
 
     @Override
@@ -66,9 +67,9 @@ public class UpdateProfileProfile extends AbstractProfile implements Profile
     public static class UpdateProfileProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
         {
-            return new UpdateProfileProfile( identifier, storedConfiguration );
+            return new UpdateProfileProfile( domainID, identifier, storedConfiguration );
         }
     }
 }

+ 47 - 9
server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java

@@ -24,6 +24,7 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.option.RecoveryMinLifetimeOption;
 import password.pwm.config.option.WebServiceUsage;
@@ -39,6 +40,7 @@ import password.pwm.util.logging.PwmLogger;
 
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
@@ -46,13 +48,13 @@ class ConfigurationCleaner
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigurationCleaner.class );
 
-    private static final List<PwmExceptionLoggingConsumer<StoredConfigurationModifier>> STORED_CONFIG_POST_PROCESSORS =
-            List.of(
+    private static final List<PwmExceptionLoggingConsumer<StoredConfigurationModifier>> STORED_CONFIG_POST_PROCESSORS = List.of(
                     new UpdateDeprecatedAdComplexitySettings(),
                     new UpdateDeprecatedMinPwdLifetimeSetting(),
                     new UpdateDeprecatedPublicHealthSetting(),
                     new ProfileNonProfiledSettings(),
-                    new CheckForSuperfluousProfileSettings() );
+                    new CheckForSuperfluousProfileSettings(),
+                    new RemoveDefaultSettings() );
 
     static void postProcessStoredConfig(
             final StoredConfigurationModifier storedConfiguration
@@ -124,7 +126,7 @@ class ConfigurationCleaner
             final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
             for ( final DomainID domainID : StoredConfigurationUtil.domainList( oldConfig ) )
             {
-                for ( final String profileID : StoredConfigurationUtil.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, oldConfig ) )
+                for ( final String profileID : StoredConfigurationUtil.profilesForSetting( domainID, PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, oldConfig ) )
                 {
                     final StoredConfigKey key = StoredConfigKey.forSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID, domainID );
                     final Optional<StoredValue> oldValue = oldConfig.readStoredValue( key );
@@ -191,8 +193,7 @@ class ConfigurationCleaner
         {
             final StoredConfiguration inputConfig = modifier.newStoredConfiguration();
             inputConfig.keys()
-                    .parallel()
-                    .filter( ( key ) -> StoredConfigKey.RecordType.SETTING.equals( key.getRecordType() ) )
+                    .filter( ( key ) -> key.isRecordType( StoredConfigKey.RecordType.SETTING ) )
                     .filter( ( key ) -> key.toPwmSetting().getCategory().hasProfiles() )
                     .filter( ( key ) -> StringUtil.isEmpty( key.getProfileID() ) )
                     .forEach( ( key ) -> convertSetting( inputConfig, modifier, key ) );
@@ -205,7 +206,7 @@ class ConfigurationCleaner
         {
             final PwmSetting pwmSetting = key.toPwmSetting();
 
-            final List<String> targetProfiles = StoredConfigurationUtil.profilesForSetting( pwmSetting, inputConfig );
+            final List<String> targetProfiles = StoredConfigurationUtil.profilesForSetting(  key.getDomainID(), pwmSetting, inputConfig );
             final StoredValue value = inputConfig.readStoredValue( key ).orElseThrow();
             final Optional<ValueMetaData> valueMetaData = inputConfig.readMetaData( key );
 
@@ -246,7 +247,7 @@ class ConfigurationCleaner
         {
             final StoredConfiguration inputConfig = modifier.newStoredConfiguration();
             inputConfig.keys()
-                    .filter( ( key ) -> StoredConfigKey.RecordType.SETTING.equals( key.getRecordType() ) )
+                    .filter( ( key ) -> key.isRecordType( StoredConfigKey.RecordType.SETTING ) )
                     .filter( ( key ) -> key.toPwmSetting().getCategory().hasProfiles() )
                     .filter( ( key ) -> verifyProfileIsValid( key, inputConfig ) )
                     .forEach( ( key ) -> removeSuperfluousProfile( key, modifier ) );
@@ -256,7 +257,7 @@ class ConfigurationCleaner
         {
             final PwmSetting pwmSetting = key.toPwmSetting();
             final String recordID = key.getProfileID();
-            final List<String> profiles = StoredConfigurationUtil.profilesForSetting( pwmSetting, inputConfig );
+            final List<String> profiles = StoredConfigurationUtil.profilesForSetting( key.getDomainID(), pwmSetting, inputConfig );
             return !profiles.contains( recordID );
         }
 
@@ -273,4 +274,41 @@ class ConfigurationCleaner
             }
         }
     }
+
+    private static class RemoveDefaultSettings implements PwmExceptionLoggingConsumer<StoredConfigurationModifier>
+    {
+        @Override
+        public void accept( final StoredConfigurationModifier modifier )
+                throws PwmUnrecoverableException
+        {
+            final StoredConfiguration inputConfig = modifier.newStoredConfiguration();
+            final PwmSettingTemplateSet templateSet = inputConfig.getTemplateSet();
+            inputConfig.keys()
+                    .filter( ( key ) -> key.isRecordType( StoredConfigKey.RecordType.SETTING ) )
+                    .filter( key -> !valueIsDefault( key, inputConfig, templateSet ) )
+                    .forEach( ( key ) -> removeDefaultValue( key, inputConfig, modifier ) );
+        }
+
+        private boolean valueIsDefault( final StoredConfigKey key, final StoredConfiguration inputConfig, final PwmSettingTemplateSet pwmSettingTemplateSet )
+        {
+            final StoredValue value = inputConfig.readStoredValue( key ).orElseThrow();
+            final String loopHash = value.valueHash();
+            final String defaultHash = key.toPwmSetting().getDefaultValue( pwmSettingTemplateSet ).valueHash();
+            return !Objects.equals( loopHash, defaultHash );
+        }
+
+        private void removeDefaultValue( final StoredConfigKey key, final StoredConfiguration inputConfig, final StoredConfigurationModifier modifier )
+        {
+            try
+            {
+                final StoredValue value = inputConfig.readStoredValue( key ).orElseThrow();
+                LOGGER.info( () -> "removing setting " + key.toString() + " with default value: " + value.toDebugString( PwmConstants.DEFAULT_LOCALE ) );
+                modifier.deleteKey( key );
+            }
+            catch ( final PwmUnrecoverableException e )
+            {
+                LOGGER.warn( () -> "error deleting setting " + key.toString() + " with default value: " + e.getMessage() );
+            }
+        }
+    }
 }

+ 11 - 7
server/src/main/java/password/pwm/config/stored/StoredConfigKey.java

@@ -116,6 +116,11 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
         return new StoredConfigKey( RecordType.PROPERTY, DomainID.systemId(), configurationProperty.getKey(), null );
     }
 
+    public StoredConfigKey withNewDomain( final DomainID domainID )
+    {
+        return new StoredConfigKey( this.getRecordType(), domainID, this.getRecordID(), this.getProfileID() );
+    }
+
     public boolean isRecordType( final RecordType recordType )
     {
         return recordType != null && Objects.equals( getRecordType(), recordType );
@@ -254,7 +259,7 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
         }
         final StoredConfigKey that = ( StoredConfigKey ) o;
         return Objects.equals( recordType, that.recordType )
-                //&& Objects.equals( domainID, that.domainID )
+                && Objects.equals( domainID, that.domainID )
                 && Objects.equals( recordID, that.recordID )
                 && Objects.equals( profileID, that.profileID );
     }
@@ -262,8 +267,7 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
     @Override
     public int hashCode()
     {
-        //return Objects.hash( recordType, domainID, recordID, profileID );
-        return Objects.hash( recordType, recordID, profileID );
+        return Objects.hash( recordType, domainID, recordID, profileID );
     }
 
     @Override
@@ -324,10 +328,10 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
                 StoredConfigKey::getRecordType,
                 Comparator.nullsLast( Comparator.naturalOrder() ) );
 
-        /*
+
         final Comparator<StoredConfigKey> domainComparator = Comparator.comparing( StoredConfigKey::getDomainID,
                 Comparator.nullsLast( Comparator.naturalOrder() ) );
-        */
+
 
         final Comparator<StoredConfigKey> recordComparator = ( o1, o2 ) ->
         {
@@ -347,8 +351,8 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
                 StoredConfigKey::getProfileID,
                 Comparator.nullsLast( Comparator.naturalOrder() ) );
 
-        return typeComparator
-                //.thenComparing( domainComparator )
+        return domainComparator
+                .thenComparing( typeComparator )
                 .thenComparing( recordComparator )
                 .thenComparing( profileComparator );
     }

+ 1 - 32
server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -37,11 +37,8 @@ import java.time.Instant;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -65,9 +62,7 @@ public class StoredConfigurationImpl implements StoredConfiguration
         this.modifyTime = storedConfigData.getModifyTime();
         this.metaValues = Map.copyOf( storedConfigData.getMetaDatas() );
         this.templateSet = readTemplateSet( storedConfigData.getStoredValues() );
-
-        final Map<StoredConfigKey, StoredValue> tempMap = removeDefaultSettingValues( storedConfigData.getStoredValues(), templateSet );
-        this.storedValues = Map.copyOf( tempMap );
+        this.storedValues = Map.copyOf( storedConfigData.getStoredValues() );
     }
 
     StoredConfigurationImpl()
@@ -79,32 +74,6 @@ public class StoredConfigurationImpl implements StoredConfiguration
         this.templateSet = readTemplateSet( Collections.emptyMap() );
     }
 
-    private static Map<StoredConfigKey, StoredValue> removeDefaultSettingValues(
-            final Map<StoredConfigKey, StoredValue> valueMap,
-            final PwmSettingTemplateSet pwmSettingTemplateSet
-    )
-    {
-        final Predicate<Map.Entry<StoredConfigKey, StoredValue>> checkIfValueIsDefault = entry ->
-        {
-            if ( StoredConfigKey.RecordType.SETTING.equals( entry.getKey().getRecordType() ) )
-            {
-                final String loopHash = entry.getValue().valueHash();
-                final String defaultHash = entry.getKey().toPwmSetting().getDefaultValue( pwmSettingTemplateSet ).valueHash();
-                return !Objects.equals( loopHash, defaultHash );
-            }
-            return true;
-        };
-
-        final Map<StoredConfigKey, StoredValue> results = valueMap.entrySet()
-                .parallelStream()
-                .filter( checkIfValueIsDefault )
-                .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
-
-        return Collections.unmodifiableMap( results );
-    }
-
-
-
     @Override
     public StoredConfiguration copy()
     {

+ 53 - 5
server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java

@@ -69,6 +69,7 @@ public abstract class StoredConfigurationUtil
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationUtil.class );
 
     public static List<String> profilesForSetting(
+            final DomainID domainID,
             final PwmSetting pwmSetting,
             final StoredConfiguration storedConfiguration
     )
@@ -88,24 +89,26 @@ public abstract class StoredConfigurationUtil
             profileSetting = pwmSetting.getCategory().getProfileSetting().orElseThrow( IllegalStateException::new );
         }
 
-        return profilesForProfileSetting( profileSetting, storedConfiguration );
+        return profilesForProfileSetting( domainID, profileSetting, storedConfiguration );
     }
 
     public static List<String> profilesForCategory(
+            final DomainID domainID,
             final PwmSettingCategory category,
             final StoredConfiguration storedConfiguration
     )
     {
         final PwmSetting profileSetting = category.getProfileSetting().orElseThrow( IllegalStateException::new );
-        return profilesForProfileSetting( profileSetting, storedConfiguration );
+        return profilesForProfileSetting( domainID, profileSetting, storedConfiguration );
     }
 
     private static List<String> profilesForProfileSetting(
+            final DomainID domainID,
             final PwmSetting profileSetting,
             final StoredConfiguration storedConfiguration
     )
     {
-        final StoredConfigKey key = StoredConfigKey.forSetting( profileSetting, null, PwmConstants.DOMAIN_ID_PLACEHOLDER );
+        final StoredConfigKey key = StoredConfigKey.forSetting( profileSetting, null, domainID );
         final StoredValue storedValue = StoredConfigurationUtil.getValueOrDefault( storedConfiguration, key );
         final List<String> settingValues = ValueTypeConverter.valueToStringArray( storedValue );
         return settingValues.stream()
@@ -283,7 +286,7 @@ public abstract class StoredConfigurationUtil
         {
             if ( loopSetting.getCategory().hasProfiles() )
             {
-                return StoredConfigurationUtil.profilesForSetting( loopSetting, storedConfiguration )
+                return StoredConfigurationUtil.profilesForSetting( domainID, loopSetting, storedConfiguration )
                         .stream()
                         .map( profileId -> StoredConfigKey.forSetting( loopSetting, profileId, domainID ) )
                         .collect( Collectors.toList() )
@@ -390,7 +393,7 @@ public abstract class StoredConfigurationUtil
         }
 
         final PwmSetting profileSetting = category.getProfileSetting().orElseThrow( IllegalStateException::new );
-        final List<String> existingProfiles = StoredConfigurationUtil.profilesForSetting( profileSetting, oldStoredConfiguration );
+        final List<String> existingProfiles = StoredConfigurationUtil.profilesForSetting( domainID, profileSetting, oldStoredConfiguration );
         if ( !existingProfiles.contains( sourceID ) )
         {
             throw PwmUnrecoverableException.newException(
@@ -431,6 +434,51 @@ public abstract class StoredConfigurationUtil
         return modifier.newStoredConfiguration();
     }
 
+    public static StoredConfiguration copyDomainID(
+            final StoredConfiguration oldStoredConfiguration,
+            final String source,
+            final String destination,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final DomainID sourceID = DomainID.create( source );
+        final DomainID destinationID = DomainID.create( destination );
+
+        final List<DomainID> existingProfiles = StoredConfigurationUtil.domainList( oldStoredConfiguration );
+
+        if ( !existingProfiles.contains( sourceID ) )
+        {
+            throw PwmUnrecoverableException.newException(
+                    PwmError.ERROR_INVALID_CONFIG, "can not copy domain ID, source domainID '" + sourceID + "' does not exist" );
+        }
+
+        if ( existingProfiles.contains( destinationID ) )
+        {
+            throw PwmUnrecoverableException.newException(
+                    PwmError.ERROR_INVALID_CONFIG, "can not copy domain ID for category, destination domainID '" + destinationID + "' already exists" );
+        }
+
+        final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( oldStoredConfiguration );
+        modifier.newStoredConfiguration().keys()
+                .filter( key -> key.getDomainID().equals( sourceID ) )
+                .forEach( key ->
+        {
+            final StoredConfigKey newKey = key.withNewDomain( destinationID );
+            final StoredValue storedValue = oldStoredConfiguration.readStoredValue( key ).orElseThrow();
+            try
+            {
+                modifier.writeSetting( newKey, storedValue, userIdentity );
+            }
+            catch ( final PwmUnrecoverableException e )
+            {
+                throw new IllegalStateException( "unexpected error copying domain setting values: " + e.getMessage() );
+            }
+        } );
+
+        return modifier.newStoredConfiguration();
+    }
+
     public static String valueHash( final StoredConfiguration storedConfiguration )
     {
         final Instant startTime = Instant.now();

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

@@ -164,7 +164,7 @@ public class ConfigurationChecker implements HealthChecker
         public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
         {
             final List<HealthRecord> records = new ArrayList<>();
-            final String siteUrl = config.readSettingAsString( PwmSetting.PWM_SITE_URL );
+            final String siteUrl = config.getAppConfig().readSettingAsString( PwmSetting.PWM_SITE_URL );
             final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
 
             if ( siteUrl == null || siteUrl.isEmpty() || siteUrl.equals(

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

@@ -209,6 +209,8 @@ public class PwmSession implements Serializable
 
         UserIdentity userIdentity = null;
         String userID = null;
+        String domain = null;
+        String profile = null;
 
         if ( isAuthenticated() )
         {
@@ -217,6 +219,8 @@ public class PwmSession implements Serializable
                 final UserInfo userInfo = getUserInfo();
                 userIdentity = userInfo.getUserIdentity();
                 userID = userInfo.getUsername();
+                profile = userIdentity.getLdapProfileID();
+                domain = userIdentity.getDomainID().toString();
             }
             catch ( final PwmUnrecoverableException e )
             {
@@ -228,6 +232,8 @@ public class PwmSession implements Serializable
                 .sessionID( ssBean.getSessionID() )
                 .userID( userIdentity == null ? null : userIdentity.toDelimitedKey() )
                 .username( userID )
+                .domain( domain )
+                .profile( profile )
                 .sourceAddress( ssBean.getSrcAddress() )
                 .sourceHostname( ssBean.getSrcHostname() )
                 .build();
@@ -334,7 +340,7 @@ public class PwmSession implements Serializable
         }
 
         final LocalSessionStateBean ssBean = this.getSessionStateBean();
-        final List<Locale> knownLocales = pwmDomain.getConfig().getKnownLocales();
+        final List<Locale> knownLocales = pwmRequest.getAppConfig().getKnownLocales();
         final Locale requestedLocale = LocaleHelper.parseLocaleString( localeString );
         if ( knownLocales.contains( requestedLocale ) || "default".equalsIgnoreCase( localeString ) )
         {

+ 33 - 26
server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -27,7 +27,6 @@ import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.config.AppConfig;
-import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -74,6 +73,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 
 public class RequestInitializationFilter implements Filter
 {
@@ -432,23 +432,26 @@ public class RequestInitializationFilter implements Filter
     }
 
 
-    public static String readUserHostname( final HttpServletRequest request, final DomainConfig config ) throws PwmUnrecoverableException
+    public static Optional<String> readUserHostname( final HttpServletRequest request, final AppConfig config ) throws PwmUnrecoverableException
     {
-        if ( config != null && !config.getAppConfig().readSettingAsBoolean( PwmSetting.REVERSE_DNS_ENABLE ) )
+        if ( config != null && !config.readSettingAsBoolean( PwmSetting.REVERSE_DNS_ENABLE ) )
         {
-            return "";
+            return Optional.empty();
         }
 
-        final String userIPAddress = readUserNetworkAddress( request, config );
-        try
-        {
-            return InetAddress.getByName( userIPAddress ).getCanonicalHostName();
-        }
-        catch ( final UnknownHostException e )
+        final Optional<String> userIPAddress = readUserNetworkAddress( request, config );
+        if ( userIPAddress.isPresent() )
         {
-            LOGGER.trace( () -> "unknown host while trying to compute hostname for src request: " + e.getMessage() );
+            try
+            {
+                return Optional.of( InetAddress.getByName( userIPAddress.get() ).getCanonicalHostName() );
+            }
+            catch ( final UnknownHostException e )
+            {
+                LOGGER.trace( () -> "unknown host while trying to compute hostname for src request: " + e.getMessage() );
+            }
         }
-        return "";
+        return Optional.empty();
     }
 
     /**
@@ -459,9 +462,9 @@ public class RequestInitializationFilter implements Filter
      * @param config the application configuration
      * @return String containing the textual representation of the source IP address, or null if the request is invalid.
      */
-    public static String readUserNetworkAddress(
+    public static Optional<String> readUserNetworkAddress(
             final HttpServletRequest request,
-            final DomainConfig config
+            final AppConfig config
     )
     {
         final List<String> candidateAddresses = new ArrayList<>();
@@ -487,7 +490,7 @@ public class RequestInitializationFilter implements Filter
             final String trimAddr = candidateAddress.trim();
             if ( InetAddressValidator.getInstance().isValid( trimAddr ) )
             {
-                return trimAddr;
+                return Optional.of( trimAddr );
             }
             else
             {
@@ -495,7 +498,7 @@ public class RequestInitializationFilter implements Filter
             }
         }
 
-        return "";
+        return Optional.empty();
     }
 
     private static void handleRequestInitialization(
@@ -516,13 +519,13 @@ public class RequestInitializationFilter implements Filter
         // mark session ip address
         if ( ssBean.getSrcAddress() == null )
         {
-            ssBean.setSrcAddress( readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getDomainConfig() ) );
+            ssBean.setSrcAddress( readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getAppConfig() ).orElse( "" ) );
         }
 
         // mark the user's hostname in the session bean
         if ( ssBean.getSrcHostname() == null )
         {
-            ssBean.setSrcHostname( readUserHostname( pwmRequest.getHttpServletRequest(), pwmRequest.getDomainConfig() ) );
+            ssBean.setSrcHostname( readUserHostname( pwmRequest.getHttpServletRequest(), pwmRequest.getAppConfig() ).orElse( "" ) );
         }
 
         // update the privateUrlAccessed flag
@@ -555,7 +558,7 @@ public class RequestInitializationFilter implements Filter
         }
         else
         {
-            final List<Locale> knownLocales = pwmRequest.getDomainConfig().getKnownLocales();
+            final List<Locale> knownLocales = pwmRequest.getAppConfig().getKnownLocales();
             final Locale userLocale = LocaleHelper.localeResolver( pwmRequest.getHttpServletRequest().getLocale(), knownLocales );
             pwmRequest.getPwmSession().getSessionStateBean().setLocale( userLocale == null ? PwmConstants.DEFAULT_LOCALE : userLocale );
             LOGGER.trace( pwmRequest, () -> "user locale set to '" + pwmRequest.getLocale() + "'" );
@@ -601,16 +604,20 @@ public class RequestInitializationFilter implements Filter
     private static void checkIfSourceAddressChanged( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        if ( !pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.MULTI_IP_SESSION_ALLOWED ) )
+        if ( !pwmRequest.getAppConfig().readSettingAsBoolean( PwmSetting.MULTI_IP_SESSION_ALLOWED ) )
         {
-            final String remoteAddress = readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getDomainConfig() );
+            final Optional<String> optionalRemoteAddress = readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getAppConfig() );
             final LocalSessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
 
-            if ( !ssBean.getSrcAddress().equals( remoteAddress ) )
+            if ( optionalRemoteAddress.isPresent() )
             {
-                final String errorMsg = "current network address '" + remoteAddress + "' has changed from original network address '" + ssBean.getSrcAddress() + "'";
-                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
-                throw new PwmUnrecoverableException( errorInformation );
+                final String remoteAddress = optionalRemoteAddress.get();
+                if ( !ssBean.getSrcAddress().equals( remoteAddress ) )
+                {
+                    final String errorMsg = "current network address '" + remoteAddress + "' has changed from original network address '" + ssBean.getSrcAddress() + "'";
+                    final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
+                    throw new PwmUnrecoverableException( errorInformation );
+                }
             }
         }
     }
@@ -637,7 +644,7 @@ public class RequestInitializationFilter implements Filter
     private static void checkRequiredHeaders( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        final List<String> requiredHeaders = pwmRequest.getDomainConfig().readSettingAsStringArray( PwmSetting.REQUIRED_HEADERS );
+        final List<String> requiredHeaders = pwmRequest.getAppConfig().readSettingAsStringArray( PwmSetting.REQUIRED_HEADERS );
         if ( requiredHeaders != null && !requiredHeaders.isEmpty() )
         {
             final Map<String, String> configuredValues = StringUtil.convertStringListToNameValuePair( requiredHeaders, "=" );

+ 1 - 1
server/src/main/java/password/pwm/http/filter/SessionFilter.java

@@ -452,7 +452,7 @@ public class SessionFilter extends AbstractPwmFilter
         final String localeParamName = config.readAppProperty( AppProperty.HTTP_PARAM_NAME_LOCALE );
         final String localeCookieName = config.readAppProperty( AppProperty.HTTP_COOKIE_LOCALE_NAME );
         final String requestedLocale = pwmRequest.readParameterAsString( localeParamName );
-        final int cookieAgeSeconds = ( int ) pwmRequest.getDomainConfig().readSettingAsLong( PwmSetting.LOCALE_COOKIE_MAX_AGE );
+        final int cookieAgeSeconds = ( int ) pwmRequest.getAppConfig().readSettingAsLong( PwmSetting.LOCALE_COOKIE_MAX_AGE );
         if ( requestedLocale != null && requestedLocale.length() > 0 )
         {
             LOGGER.debug( pwmRequest, () -> "detected locale request parameter " + localeParamName + " with value " + requestedLocale );

+ 7 - 4
server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java

@@ -24,12 +24,13 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import lombok.Data;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
-import password.pwm.PwmDomain;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.SelectableContextMode;
+import password.pwm.config.option.WebServiceUsage;
 import password.pwm.config.profile.ChangePasswordProfile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.error.ErrorInformation;
@@ -75,6 +76,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.ResourceBundle;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 
@@ -403,12 +405,12 @@ public class ClientApiServlet extends ControlledPwmServlet
             final Map<String, String> localeDisplayNames = new LinkedHashMap<>();
             final Map<String, String> localeFlags = new LinkedHashMap<>();
 
-            final List<Locale> knownLocales = new ArrayList<>( pwmDomain.getConfig().getKnownLocales() );
+            final List<Locale> knownLocales = new ArrayList<>( pwmRequest.getAppConfig().getKnownLocales() );
             knownLocales.sort( LocaleHelper.localeComparator( PwmConstants.DEFAULT_LOCALE ) );
 
             for ( final Locale locale : knownLocales )
             {
-                final String flagCode = pwmDomain.getConfig().getKnownLocaleFlagMap().get( locale );
+                final String flagCode = pwmRequest.getAppConfig().getKnownLocaleFlagMap().get( locale );
                 localeFlags.put( locale.toString(), flagCode );
                 localeInfo.put( locale.toString(), locale.getDisplayName( PwmConstants.DEFAULT_LOCALE ) + " - " + locale.getDisplayLanguage( userLocale ) );
                 localeDisplayNames.put( locale.toString(), locale.getDisplayLanguage() );
@@ -521,7 +523,8 @@ public class ClientApiServlet extends ControlledPwmServlet
             throw new PwmUnrecoverableException( errorInformation );
         }
 
-        if ( !pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES ) )
+        final Set<WebServiceUsage> enabledUsages = pwmRequest.getDomainConfig().readSettingAsOptionList( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, WebServiceUsage.class );
+        if ( !enabledUsages.contains( WebServiceUsage.Health ) && !enabledUsages.contains( WebServiceUsage.Statistics ) )
         {
             if ( !pwmRequest.isAuthenticated() )
             {

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

@@ -23,6 +23,7 @@ package password.pwm.http.servlet;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.option.WebServiceUsage;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -38,6 +39,7 @@ import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Locale;
+import java.util.Set;
 
 @WebServlet(
         name = "FullPageHealthServlet",
@@ -92,7 +94,8 @@ public class FullPageHealthServlet extends ControlledPwmServlet
     public ProcessStatus preProcessCheck( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException, IOException, ServletException
     {
-        if ( !pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES ) )
+        final Set<WebServiceUsage> enabledUsages = pwmRequest.getDomainConfig().readSettingAsOptionList( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, WebServiceUsage.class );
+        if ( !enabledUsages.contains( WebServiceUsage.Health ) && !enabledUsages.contains( WebServiceUsage.Statistics ) )
         {
             final Locale locale = pwmRequest.getLocale();
             final String errorMsg = "configuration setting "

+ 29 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -149,6 +149,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         testMacro( HttpMethod.POST ),
         browseLdap( HttpMethod.POST ),
         copyProfile( HttpMethod.POST ),
+        copyDomain( HttpMethod.POST ),
         randomPassword( HttpMethod.POST ),;
 
         private final HttpMethod method;
@@ -890,7 +891,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
 
     @ActionHandler( action = "copyProfile" )
     private ProcessStatus restCopyProfile( final PwmRequest pwmRequest )
-            throws IOException, ServletException, PwmUnrecoverableException
+            throws IOException, PwmUnrecoverableException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation );
@@ -926,6 +927,33 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         return ProcessStatus.Halt;
     }
 
+    @ActionHandler( action = "copyDomain" )
+    private ProcessStatus restCopyDomain( final PwmRequest pwmRequest )
+            throws IOException, PwmUnrecoverableException
+    {
+        final ConfigManagerBean configManagerBean = getBean( pwmRequest );
+        final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation );
+        final String sourceID = inputMap.get( "sourceID" );
+        final String destinationID = inputMap.get( "destinationID" );
+
+        try
+        {
+            final StoredConfiguration newStoredConfig = StoredConfigurationUtil.copyDomainID(
+                    configManagerBean.getStoredConfiguration(),
+                    sourceID,
+                    destinationID,
+                    pwmRequest.getUserInfoIfLoggedIn() );
+            pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
+            configManagerBean.setStoredConfiguration( newStoredConfig );
+        }
+        catch ( final PwmUnrecoverableException e )
+        {
+            pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) );
+        }
+
+        return ProcessStatus.Halt;
+    }
+
     @ActionHandler( action = "randomPassword" )
     private ProcessStatus restRandomPassword( final PwmRequest pwmRequest )
             throws IOException, PwmUnrecoverableException

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

@@ -205,7 +205,7 @@ public class ConfigEditorServletUtils
         {
             final Map<String, String> defaultValueMap = new LinkedHashMap<>();
             final String defaultLocaleValue = ResourceBundle.getBundle( pwmLocaleBundle.getTheClass().getName(), PwmConstants.DEFAULT_LOCALE ).getString( keyName );
-            for ( final Locale locale : pwmRequest.getDomainConfig().getKnownLocales() )
+            for ( final Locale locale : pwmRequest.getAppConfig().getKnownLocales() )
             {
                 final ResourceBundle localeBundle = ResourceBundle.getBundle( pwmLocaleBundle.getTheClass().getName(), locale );
                 if ( locale.toString().equalsIgnoreCase( PwmConstants.DEFAULT_LOCALE.toString() ) )

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java

@@ -171,7 +171,7 @@ public class NavTreeDataMaker
             final Map<String, String> storedBundle = storedConfiguration.readLocaleBundleMap( bundle, key );
             if ( !storedBundle.isEmpty() )
             {
-                for ( final Locale locale : config.getKnownLocales() )
+                for ( final Locale locale : config.getAppConfig().getKnownLocales() )
                 {
                     final ResourceBundle defaultBundle = ResourceBundle.getBundle( bundle.getTheClass().getName(), locale );
                     final String localeKeyString = PwmConstants.DEFAULT_LOCALE.toString().equals( locale.toString() ) ? "" : locale.toString();
@@ -213,7 +213,7 @@ public class NavTreeDataMaker
             }
             else
             {
-                final List<String> profiles = StoredConfigurationUtil.profilesForCategory( loopCategory, storedConfiguration );
+                final List<String> profiles = StoredConfigurationUtil.profilesForCategory( pwmDomain.getDomainID(), loopCategory, storedConfiguration );
 
                 if ( loopCategory.isTopLevelProfile() )
                 {

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java

@@ -91,7 +91,7 @@ public class SettingDataMaker
                         LinkedHashMap::new ) ) );
 
         final VarData varMap = VarData.builder()
-                .ldapProfileIds( StoredConfigurationUtil.profilesForSetting( PwmSetting.LDAP_PROFILE_LIST, storedConfiguration ) )
+                .ldapProfileIds( StoredConfigurationUtil.profilesForSetting( PwmConstants.DOMAIN_ID_PLACEHOLDER, PwmSetting.LDAP_PROFILE_LIST, storedConfiguration ) )
                 .domainIds( StoredConfigurationUtil.domainList( storedConfiguration ).stream().map( DomainID::stringValue ).collect( Collectors.toList() ) )
                 .currentTemplate( templateSet )
                 .build();

+ 1 - 1
server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java

@@ -237,7 +237,7 @@ class CryptoCookieLoginImpl implements SessionLoginProvider
 
         if ( loginInfoBean.getAuthTime() != null )
         {
-            final long sessionMaxSeconds = pwmRequest.getDomainConfig().readSettingAsLong( PwmSetting.SESSION_MAX_SECONDS );
+            final long sessionMaxSeconds = pwmRequest.getAppConfig().readSettingAsLong( PwmSetting.SESSION_MAX_SECONDS );
             final TimeDuration sessionTotalAge = TimeDuration.fromCurrent( loginInfoBean.getAuthTime() );
             final TimeDuration sessionMaxAge = TimeDuration.of( sessionMaxSeconds, TimeDuration.Unit.SECONDS );
             if ( sessionTotalAge.isLongerThan( sessionMaxAge ) )

+ 1 - 1
server/src/main/java/password/pwm/http/tag/value/PwmValue.java

@@ -274,7 +274,7 @@ public enum PwmValue
         @Override
         public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
         {
-            final String flagFileName = pwmRequest.getDomainConfig().getKnownLocaleFlagMap().get( pwmRequest.getLocale() );
+            final String flagFileName = pwmRequest.getAppConfig().getKnownLocaleFlagMap().get( pwmRequest.getLocale() );
             return flagFileName == null ? "" : flagFileName;
         }
     }

+ 4 - 4
server/src/main/java/password/pwm/svc/email/EmailServerUtil.java

@@ -73,18 +73,18 @@ public class EmailServerUtil
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( EmailServerUtil.class );
 
-    static List<EmailServer> makeEmailServersMap( final AppConfig domainConfig )
+    static List<EmailServer> makeEmailServersMap( final AppConfig appConfig )
             throws PwmUnrecoverableException
     {
         final List<EmailServer> returnObj = new ArrayList<>(  );
 
-        final Collection<EmailServerProfile> profiles = domainConfig.getEmailServerProfiles().values();
+        final Collection<EmailServerProfile> profiles = appConfig.getEmailServerProfiles().values();
 
         for ( final EmailServerProfile profile : profiles )
         {
-            final TrustManager[] trustManager = trustManagerForProfile( domainConfig, profile );
+            final TrustManager[] trustManager = trustManagerForProfile( appConfig, profile );
 
-            final Optional<EmailServer> emailServer = makeEmailServer( domainConfig, profile, trustManager );
+            final Optional<EmailServer> emailServer = makeEmailServer( appConfig, profile, trustManager );
 
             emailServer.ifPresent( returnObj::add );
         }

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

@@ -108,7 +108,7 @@ public class LDAPPermissionCalculator implements Serializable
         {
             if ( pwmSetting.getCategory().hasProfiles() )
             {
-                final List<String> profiles = StoredConfigurationUtil.profilesForSetting( pwmSetting, domainConfig.getStoredConfiguration() );
+                final List<String> profiles = StoredConfigurationUtil.profilesForSetting( domainConfig.getDomainID(), pwmSetting, domainConfig.getStoredConfiguration() );
                 for ( final String profile : profiles )
                 {
                     permissionRecords.addAll( figureRecord( pwmSetting, profile ) );

+ 1 - 3
server/src/main/java/password/pwm/util/Validator.java

@@ -22,7 +22,6 @@ package password.pwm.util;
 
 import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
-import password.pwm.PwmDomain;
 import password.pwm.bean.FormNonce;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
@@ -53,11 +52,10 @@ public class Validator
             throws PwmUnrecoverableException
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
 
         final String submittedPwmFormID = pwmRequest.readParameterAsString( PwmConstants.PARAM_FORM_ID );
 
-        if ( pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.SECURITY_ENABLE_FORM_NONCE ) )
+        if ( pwmRequest.getAppConfig().readSettingAsBoolean( PwmSetting.SECURITY_ENABLE_FORM_NONCE ) )
         {
             final FormNonce formNonce = pwmRequest.getPwmDomain().getSecureService().decryptObject(
                     submittedPwmFormID,

+ 2 - 2
server/src/main/java/password/pwm/util/i18n/LocaleHelper.java

@@ -331,7 +331,7 @@ public class LocaleHelper
         final Map<Locale, String> returnObj = new LinkedHashMap<>();
         final Collection<Locale> localeList = domainConfig == null
                 ? new ArrayList<>( PwmConstants.INCLUDED_LOCALES )
-                : new ArrayList<>( domainConfig.getKnownLocales() );
+                : new ArrayList<>( domainConfig.getAppConfig().getKnownLocales() );
 
         final String defaultValue = getLocalizedMessage( defaultLocale, key, domainConfig, bundleClass );
         returnObj.put( defaultLocale, defaultValue );
@@ -388,7 +388,7 @@ public class LocaleHelper
         {
             for ( final String key : pwmLocaleBundle.getDisplayKeys() )
             {
-                for ( final Locale locale : domainConfig.getKnownLocales() )
+                for ( final Locale locale : domainConfig.getAppConfig().getKnownLocales() )
                 {
                     final String defaultValue = LocaleHelper.getLocalizedMessage( locale, key, null, pwmLocaleBundle.getTheClass() );
                     final String customizedValue = LocaleHelper.getLocalizedMessage( locale, key, domainConfig, pwmLocaleBundle.getTheClass() );

+ 2 - 0
server/src/main/java/password/pwm/util/logging/PwmLogEvent.java

@@ -51,6 +51,7 @@ public class PwmLogEvent implements Serializable, Comparable<PwmLogEvent>
     private final String message;
     private final Throwable throwable;
     private final String username;
+    private final String domain;
     private final String sourceAddress;
 
     private static final Comparator<PwmLogEvent> COMPARATOR = Comparator.comparing(
@@ -104,6 +105,7 @@ public class PwmLogEvent implements Serializable, Comparable<PwmLogEvent>
         this.sessionID = sessionLabel == null ? "" : StringUtil.truncate( sessionLabel.getSessionID(), 256 );
         this.requestID = sessionLabel == null ? "" : StringUtil.truncate( sessionLabel.getRequestID(), 256 );
         this.username = sessionLabel == null ? "" : StringUtil.truncate( sessionLabel.getUsername(), 256 );
+        this.domain = sessionLabel == null ? "" : StringUtil.truncate( sessionLabel.getDomain(), 256 );
         this.sourceAddress = sessionLabel == null ? "" : StringUtil.truncate( sessionLabel.getSourceAddress(), 256 );
     }
 

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

@@ -457,7 +457,7 @@ public class RandomPasswordGenerator
     {
         if ( config != null && password != null )
         {
-            final List<String> disallowedInputs = config.readSettingAsStringArray( PwmSetting.DISALLOWED_HTTP_INPUTS );
+            final List<String> disallowedInputs = config.getAppConfig().readSettingAsStringArray( PwmSetting.DISALLOWED_HTTP_INPUTS );
             for ( final String loopRegex : disallowedInputs )
             {
                 if ( password.matches( loopRegex ) )

+ 2 - 2
server/src/main/java/password/pwm/ws/server/RestServlet.java

@@ -102,8 +102,8 @@ public abstract class RestServlet extends HttpServlet
         {
             sessionLabel =  SessionLabel.builder()
                     .sessionID( "rest-" + REQUEST_COUNTER.next() )
-                    .sourceAddress( RequestInitializationFilter.readUserNetworkAddress( req, pwmApplication.getDefaultDomain().getConfig() ) )
-                    .sourceHostname( RequestInitializationFilter.readUserHostname( req, pwmApplication.getDefaultDomain().getConfig() ) )
+                    .sourceAddress( RequestInitializationFilter.readUserNetworkAddress( req, pwmApplication.getConfig() ).orElse( "" ) )
+                    .sourceHostname( RequestInitializationFilter.readUserHostname( req, pwmApplication.getConfig() ).orElse( "" ) )
                     .build();
         }
         catch ( final PwmUnrecoverableException e )

+ 6 - 2
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -57,6 +57,10 @@
     </setting>
     <setting hidden="false" key="domain.list" level="0">
         <regex>^(?!.*system.*)([a-zA-Z][a-zA-Z0-9]{2,10})$</regex>
+        <properties>
+            <property key="Minimum">1</property>
+            <property key="Minimum">1000</property>
+        </properties>
         <default>
             <value>default</value>
         </default>
@@ -4194,7 +4198,7 @@
     </category>
     <category hidden="false" scope="DOMAIN" key="GENERAL">
     </category>
-    <category hidden="false" scope="DOMAIN" key="LOCALIZATION">
+    <category hidden="false" scope="SYSTEM" key="LOCALIZATION">
     </category>
     <category hidden="false" scope="DOMAIN" key="TELEMETRY">
     </category>
@@ -4216,7 +4220,7 @@
     <category hidden="false" scope="DOMAIN" key="CHALLENGE_POLICY">
         <profile setting="challenge.profile.list"/>
     </category>
-    <category hidden="false" scope="DOMAIN" key="EMAIL_SERVERS">
+    <category hidden="false" scope="SYSTEM" key="EMAIL_SERVERS">
         <profile setting="email.profile.list"/>
     </category>
     <category hidden="false" scope="DOMAIN" key="EMAIL">

+ 1 - 0
server/src/main/resources/password/pwm/i18n/Config.properties

@@ -38,6 +38,7 @@ Button_ShowAdvanced=Show %1% Advanced Settings
 Button_HideAdvanced=Hide Advanced Settings
 Button_AddPermission=Add Users
 Confirm_ConfigPasswordStored=The configuration password has been changed; please click the "save" icon to store the new password.
+Confirm_RemoveDomain=Are you sure you want to remove the domain <code>%1%</code>?  ALL setting values associated with this profile will also be removed.
 Confirm_RemoveProfile=Are you sure you want to remove the profile <code>%1%</code>?  The setting values associated with this profile will also be removed.
 Confirm_LockConfig=Are you sure you want to restrict the configuration?  After you restrict the configuration, you must authenticate using your LDAP directory credentials before authenticating, so be sure your LDAP configuration is working properly before restricting.
 Confirm_SkipGuide=Are you sure you want to skip the configuration guide?

+ 2 - 1
server/src/test/java/password/pwm/config/stored/ConfigurationCleanerTest.java

@@ -73,7 +73,8 @@ public class ConfigurationCleanerTest
     @Test
     public void testProfiledSettings()
     {
-        final List<String> profileList = StoredConfigurationUtil.profilesForSetting( PwmSetting.PEOPLE_SEARCH_PHOTO_QUERY_FILTER, domainConfig.getStoredConfiguration() );
+        final List<String> profileList = StoredConfigurationUtil.profilesForSetting(
+                PwmConstants.DOMAIN_ID_PLACEHOLDER, PwmSetting.PEOPLE_SEARCH_PHOTO_QUERY_FILTER, domainConfig.getStoredConfiguration() );
         Assert.assertEquals( 1, profileList.size() );
 
         final PeopleSearchProfile peopleSearchProfile = domainConfig.getPeopleSearchProfiles().get( PwmConstants.PROFILE_ID_DEFAULT );

+ 1 - 1
server/src/test/java/password/pwm/config/stored/StoredConfigurationModifierTest.java

@@ -73,7 +73,7 @@ public class StoredConfigurationModifierTest
                 "newProfile",
                 null );
 
-        final List<String> profileNames = StoredConfigurationUtil.profilesForSetting( PwmSetting.HELPDESK_RESULT_LIMIT, postCopyConfig );
+        final List<String> profileNames = StoredConfigurationUtil.profilesForSetting( PwmConstants.DOMAIN_ID_PLACEHOLDER, PwmSetting.HELPDESK_RESULT_LIMIT, postCopyConfig );
         Assert.assertEquals( 2, profileNames.size() );
         Assert.assertTrue( profileNames.contains( "default" ) );
         Assert.assertTrue( profileNames.contains( "newProfile" ) );

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

@@ -25,7 +25,6 @@ import org.junit.Test;
 import org.mockito.Mockito;
 import password.pwm.bean.DomainID;
 import password.pwm.config.AppConfig;
-import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfigurationFactory;
@@ -35,6 +34,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpHeader;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.Optional;
 
 public class RequestInitializationFilterTest
 {
@@ -42,11 +42,11 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTest()
             throws PwmUnrecoverableException
     {
-        final DomainConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf ).orElseThrow();
         Assert.assertEquals( "10.1.1.1", resultIP );
     }
 
@@ -54,24 +54,24 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestBogus()
             throws PwmUnrecoverableException
     {
-        final DomainConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1m" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
-        Assert.assertEquals( "", resultIP );
+        final Optional<String> resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        Assert.assertTrue( resultIP.isEmpty() );
     }
 
     @Test
     public void readUserNetworkAddressTestXForward()
             throws PwmUnrecoverableException
     {
-        final DomainConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf ).orElseThrow();
         Assert.assertEquals( "10.1.1.2", resultIP );
     }
 
@@ -79,12 +79,12 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestBogusXForward()
             throws PwmUnrecoverableException
     {
-        final DomainConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf ).orElseThrow();
         Assert.assertEquals( "10.1.1.1", resultIP );
     }
 
@@ -92,12 +92,12 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestMultipleXForward()
             throws PwmUnrecoverableException
     {
-        final DomainConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2, 10.1.1.3" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf ).orElseThrow();
         Assert.assertEquals( "10.1.1.2", resultIP );
     }
 
@@ -105,12 +105,12 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestMultipleBogusXForward()
             throws PwmUnrecoverableException
     {
-        final DomainConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a, 10.1.1.3" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf ).orElseThrow();
         Assert.assertEquals( "10.1.1.3", resultIP );
     }
 
@@ -118,12 +118,12 @@ public class RequestInitializationFilterTest
     public void readUserNetworkAddressTestIPv6()
             throws PwmUnrecoverableException
     {
-        final DomainConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( StoredConfigurationFactory.newConfig() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2a, 2001:0db8:85a3:0000:0000:8a2e:0370:7334" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf ).orElseThrow();
         Assert.assertEquals( "2001:0db8:85a3:0000:0000:8a2e:0370:7334", resultIP );
     }
 
@@ -134,12 +134,12 @@ public class RequestInitializationFilterTest
         final StoredConfigurationModifier modifier = StoredConfigurationFactory.newModifiableConfig();
         final StoredConfigKey key = StoredConfigKey.forSetting( PwmSetting.USE_X_FORWARDED_FOR_HEADER, null, DomainID.systemId() );
         modifier.writeSetting( key, BooleanValue.of( false ), null );
-        final DomainConfig conf = new AppConfig( modifier.newStoredConfiguration() ).getDefaultDomainConfig();
+        final AppConfig conf = new AppConfig( modifier.newStoredConfiguration() );
         final HttpServletRequest mockRequest = Mockito.mock( HttpServletRequest.class );
         Mockito.when( mockRequest.getRemoteAddr() ).thenReturn( "10.1.1.1" );
         Mockito.when( mockRequest.getHeader( HttpHeader.XForwardedFor.getHttpName() ) ).thenReturn( "10.1.1.2" );
 
-        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf );
+        final String resultIP = RequestInitializationFilter.readUserNetworkAddress( mockRequest, conf ).orElseThrow();
         Assert.assertEquals( "10.1.1.1", resultIP );
     }
 }

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

@@ -24,7 +24,6 @@ import org.junit.Assert;
 import org.junit.Test;
 import password.pwm.PwmConstants;
 import password.pwm.config.AppConfig;
-import password.pwm.config.DomainConfig;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.util.java.StringUtil;
 
@@ -58,8 +57,8 @@ public class NonLocalizedKeyTest
 
         // check non-default locales do NOT have value
         {
-            final DomainConfig domainConfig = new AppConfig( StoredConfigurationFactory.newConfig() ).getDefaultDomainConfig();
-            final List<Locale> locales = domainConfig.getKnownLocales();
+            final AppConfig appConfig = new AppConfig( StoredConfigurationFactory.newConfig() );
+            final List<Locale> locales = appConfig.getKnownLocales();
             for ( final Locale locale : locales )
             {
                 if ( !PwmConstants.DEFAULT_LOCALE.toLanguageTag().equals( locale.toLanguageTag() ) )

+ 1 - 0
webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp

@@ -206,6 +206,7 @@
 <pwm:script-ref url="/public/resources/js/configeditor-settings-customlink.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings-remotewebservices.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings-permissions.js"/>
+<pwm:script-ref url="/public/resources/js/configeditor-settings-stringarray.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor.js"/>
 <pwm:script-ref url="/public/resources/js/admin.js"/>
 

+ 2 - 2
webapp/src/main/webapp/public/localeselect.jsp

@@ -40,7 +40,7 @@
     PwmDomain localeselect_pwmDomain = null;
     try {
         localeselect_pwmDomain = PwmRequest.forRequest(request, response).getPwmDomain();
-        localeList = localeselect_pwmDomain.getConfig().getKnownLocales();
+        localeList = localeselect_pwmDomain.getPwmApplication().getConfig().getKnownLocales();
     } catch (PwmException e) {
         /* noop */
     }
@@ -56,7 +56,7 @@
                 <% for (final Locale locale : localeList) { %>
                 <tr>
                     <td>
-                        <% final String flagCode = localeselect_pwmDomain.getConfig().getKnownLocaleFlagMap().get(locale); %>
+                        <% final String flagCode = localeselect_pwmDomain.getPwmApplication().getConfig().getKnownLocaleFlagMap().get(locale); %>
                         <img alt="flag" src="<pwm:context/><pwm:url url='/public/resources/flags/png/'/><%=flagCode%>.png"/>
                     </td>
                     <td>

+ 6 - 6
webapp/src/main/webapp/public/reference/localeinfo.jsp

@@ -132,7 +132,7 @@
                     Percent Present
                 </td>
             </tr>
-            <% for (final Locale loopLocale: pwmRequest.getDomainConfig().getKnownLocales()) { %>
+            <% for (final Locale loopLocale: pwmRequest.getAppConfig().getKnownLocales()) { %>
             <% final boolean highLight =  ( highlightedLocales.contains(loopLocale)); %>
             <tr<%=highLight ? " class=\"highlight\"" : ""%>>
                 <td>
@@ -189,7 +189,7 @@
                     Percent Present
                 </td>
             </tr>
-            <% for (final Locale loopLocale: pwmRequest.getDomainConfig().getKnownLocales()) { %>
+            <% for (final Locale loopLocale: pwmRequest.getAppConfig().getKnownLocales()) { %>
             <% final boolean highLight =  ( highlightedLocales.contains(loopLocale)); %>
             <tr<%=highLight ? " class=\"highlight\"" : ""%>>
                 <td>
@@ -247,7 +247,7 @@
                     Percent Present
                 </td>
             </tr>
-            <% for (final Locale loopLocale: pwmRequest.getDomainConfig().getKnownLocales()) { %>
+            <% for (final Locale loopLocale: pwmRequest.getAppConfig().getKnownLocales()) { %>
             <% final boolean highLight =  ( highlightedLocales.contains(loopLocale)); %>
             <tr<%=highLight ? " class=\"highlight\"" : ""%>>
                 <td>
@@ -304,7 +304,7 @@
                     Percent Present
                 </td>
             </tr>
-            <% for (final Locale loopLocale: pwmRequest.getDomainConfig().getKnownLocales()) { %>
+            <% for (final Locale loopLocale: pwmRequest.getAppConfig().getKnownLocales()) { %>
             <% final boolean highLight =  ( highlightedLocales.contains(loopLocale)); %>
             <tr<%=highLight ? " class=\"highlight\"" : ""%>>
                 <td>
@@ -336,7 +336,7 @@
                     Has Default Random Challenge Questions
                 </td>
             </tr>
-            <% for (final Locale loopLocale: pwmRequest.getDomainConfig().getKnownLocales()) { %>
+            <% for (final Locale loopLocale: pwmRequest.getAppConfig().getKnownLocales()) { %>
             <% final boolean highLight =  ( highlightedLocales.contains(loopLocale)); %>
             <tr<%=highLight ? " class=\"highlight\"" : ""%>>
                 <td>
@@ -364,7 +364,7 @@
         </tr>
         <% for (final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.allValues()) { %>
         <% if (!pwmLocaleBundle.isAdminOnly()) { %>
-        <% for (final Locale locale : pwmRequest.getDomainConfig().getKnownLocales()) { %>
+        <% for (final Locale locale : pwmRequest.getAppConfig().getKnownLocales()) { %>
         <% if ( highlightedLocales.contains(locale)) { %>
         <% for (final String key : LocaleStats.missingKeysForBundleAndLocale( pwmLocaleBundle, locale )) { %>
         <tr>

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

@@ -0,0 +1,283 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 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.
+ */
+
+var StringArrayValueHandler = {};
+
+StringArrayValueHandler.init = function(keyName) {
+    console.log('StringArrayValueHandler init for ' + keyName);
+
+    var parentDiv = 'table_setting_' + keyName;
+    PWM_MAIN.getObject(parentDiv).innerHTML = '<div id="tableTop_' + keyName + '">';
+    parentDiv = PWM_MAIN.getObject('tableTop_' + keyName);
+
+    PWM_VAR['clientSettingCache'][keyName + "_options"] = PWM_VAR['clientSettingCache'][keyName + "_options"] || {};
+    PWM_VAR['clientSettingCache'][keyName + "_options"]['parentDiv'] = parentDiv;
+    PWM_CFGEDIT.clearDivElements(parentDiv, true);
+    PWM_CFGEDIT.readSetting(keyName, function(resultValue) {
+        PWM_VAR['clientSettingCache'][keyName] = resultValue;
+        StringArrayValueHandler.draw(keyName);
+
+        var syntax = PWM_SETTINGS['settings'][keyName]['syntax'];
+        if (syntax === 'PROFILE') {
+            PWM_MAIN.getObject("resetButton-" + keyName).style.display = 'none';
+            PWM_MAIN.getObject("helpButton-" + keyName).style.display = 'none';
+            PWM_MAIN.getObject("modifiedNoticeIcon-" + keyName).style.display = 'none';
+        }
+    });
+};
+
+
+StringArrayValueHandler.draw = function(settingKey) {
+    var parentDiv = PWM_VAR['clientSettingCache'][settingKey + "_options"]['parentDiv'];
+    var parentDivElement = PWM_MAIN.getObject(parentDiv);
+
+    PWM_CFGEDIT.clearDivElements(parentDiv, false);
+    var resultValue = PWM_VAR['clientSettingCache'][settingKey];
+
+    var tableElement = document.createElement("table");
+    tableElement.setAttribute("style", "border-width: 0;");
+
+    var syntax = PWM_SETTINGS['settings'][settingKey]['syntax'];
+    if (syntax === 'PROFILE') {
+        var divDescriptionElement = document.createElement("div");
+        var text = PWM_SETTINGS['settings'][settingKey]['description'];
+        text += '<br/>' + PWM_CONFIG.showString('Display_ProfileNamingRules');
+        divDescriptionElement.innerHTML = text;
+        parentDivElement.appendChild(divDescriptionElement);
+
+        var defaultProfileRow = document.createElement("tr");
+        defaultProfileRow.setAttribute("colspan", "5");
+    }
+
+    var counter = 0;
+    var itemCount = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]);
+    parentDivElement.appendChild(tableElement);
+
+    for (var i in resultValue) {
+        (function(iteration) {
+            StringArrayValueHandler.drawRow(settingKey, iteration, resultValue[iteration], itemCount, tableElement);
+            counter++;
+        })(i);
+    }
+
+    var settingProperties = PWM_SETTINGS['settings'][settingKey]['properties'];
+    if (settingProperties && 'Maximum' in settingProperties && itemCount >= settingProperties['Maximum']) {
+        // item count is already maxed out
+    } else {
+        var addItemButton = document.createElement("button");
+        addItemButton.setAttribute("type", "button");
+        addItemButton.setAttribute("class", "btn");
+        addItemButton.setAttribute("id", "button-" + settingKey + "-addItem");
+        addItemButton.innerHTML = '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>' + (syntax === 'PROFILE' ? "Add Profile" : "Add Value");
+        parentDivElement.appendChild(addItemButton);
+
+        PWM_MAIN.addEventHandler('button-' + settingKey + '-addItem', 'click', function () {
+            StringArrayValueHandler.valueHandler(settingKey, -1);
+        });
+    }
+};
+
+StringArrayValueHandler.drawRow = function(settingKey, iteration, value, itemCount, parentDivElement) {
+    var settingInfo = PWM_SETTINGS['settings'][settingKey];
+    var syntax = settingInfo['syntax'];
+
+    var inputID = 'value-' + settingKey + '-' + iteration;
+
+    var valueRow = document.createElement("tr");
+    valueRow.setAttribute("style", "border-width: 0");
+    valueRow.setAttribute("id",inputID + "_row");
+
+    var rowHtml = '';
+    if (syntax !== 'PROFILE') {
+        rowHtml = '<td id="button-' + inputID + '" style="border-width:0; width: 15px"><span class="pwm-icon pwm-icon-edit"/></td>';
+    }
+    rowHtml += '<td style=""><div class="configStringPanel" id="' + inputID + '"></div></td>';
+
+    if (syntax === 'PROFILE') {
+        var copyButtonID = 'button-' + settingKey + '-' + iteration + '-copy';
+        rowHtml += '<td class="noborder nopadding" style="width:10px" title="Copy">';
+        rowHtml += '<span id="' + copyButtonID + '" class="action-icon pwm-icon pwm-icon-copy"></span>';
+        rowHtml += '</td>';
+    } else if (syntax === 'DOMAIN') {
+        var copyButtonID = 'button-' + settingKey + '-' + iteration + '-copy';
+        rowHtml += '<td class="noborder nopadding" style="width:10px" title="Copy">';
+        rowHtml += '<span id="' + copyButtonID + '" class="action-icon pwm-icon pwm-icon-copy"></span>';
+        rowHtml += '</td>';
+    }
+
+    var showMoveButtons = syntax !== 'DOMAIN';
+    if ( showMoveButtons ) {
+        var downButtonID = 'button-' + settingKey + '-' + iteration + '-moveDown';
+        rowHtml += '<td class="noborder nopadding" style="width:10px" title="Move Down">';
+        if (itemCount > 1 && iteration !== (itemCount - 1)) {
+            rowHtml += '<span id="' + downButtonID + '" class="action-icon pwm-icon pwm-icon-chevron-down"></span>';
+        }
+        rowHtml += '</td>';
+
+        var upButtonID = 'button-' + settingKey + '-' + iteration + '-moveUp';
+        rowHtml += '<td class="noborder nopadding" style="width:10px" title="Move Up">';
+        if (itemCount > 1 && iteration !== 0) {
+            rowHtml += '<span id="' + upButtonID + '" class="action-icon pwm-icon pwm-icon-chevron-up"></span>';
+        }
+        rowHtml += '</td>';
+    }
+
+    var deleteButtonID = 'button-' + settingKey + '-' + iteration + '-delete';
+    rowHtml += '<td class="noborder nopadding" style="width:10px" title="Delete">';
+
+    if (itemCount > 1 || (!settingInfo['required'])) {
+        rowHtml += '<span id="' + deleteButtonID + '" class="delete-row-icon action-icon pwm-icon pwm-icon-times"></span>';
+    }
+    rowHtml += '</td>';
+
+
+    valueRow.innerHTML = rowHtml;
+    parentDivElement.appendChild(valueRow);
+
+    UILibrary.addTextValueToElement(inputID, value);
+    if (syntax !== 'PROFILE') {
+        PWM_MAIN.addEventHandler(inputID,'click',function(){
+            StringArrayValueHandler.valueHandler(settingKey,iteration);
+        });
+        PWM_MAIN.addEventHandler('button-' + inputID,'click',function(){
+            StringArrayValueHandler.valueHandler(settingKey,iteration);
+        });
+    } else {
+        PWM_MAIN.addEventHandler(copyButtonID,'click',function(){
+            var editorOptions = {};
+            editorOptions['title'] = 'Copy Profile - New Profile ID';
+            editorOptions['regex'] = PWM_SETTINGS['settings'][settingKey]['pattern'];
+            editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder'];
+            editorOptions['completeFunction'] = function(newValue){
+                var options = {};
+                options['setting'] = settingKey;
+                options['sourceID'] = value;
+                options['destinationID'] = newValue;
+                var resultFunction = function(data){
+                    if (data['error']) {
+                        PWM_MAIN.showErrorDialog(data);
+                    } else {
+                        PWM_MAIN.gotoUrl('editor');
+                    }
+                };
+                PWM_MAIN.showWaitDialog({loadFunction:function(){
+                        PWM_MAIN.ajaxRequest("editor?processAction=copyProfile",resultFunction,{content:options});
+                    }});
+            };
+            UILibrary.stringEditorDialog(editorOptions);
+        });
+    }
+
+    if (itemCount > 1 && iteration !== (itemCount -1)) {
+        PWM_MAIN.addEventHandler(downButtonID,'click',function(){StringArrayValueHandler.move(settingKey,false,iteration)});
+    }
+
+    if (itemCount > 1 && iteration !== 0) {
+        PWM_MAIN.addEventHandler(upButtonID,'click',function(){StringArrayValueHandler.move(settingKey,true,iteration)});
+    }
+
+    if (itemCount > 1 || !PWM_SETTINGS['settings'][settingKey]['required']) {
+        PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){StringArrayValueHandler.removeValue(settingKey,iteration)});
+    }
+};
+
+StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
+    var okAction = function(value) {
+        if (iteration > -1) {
+            PWM_VAR['clientSettingCache'][settingKey][iteration] = value;
+        } else {
+            PWM_VAR['clientSettingCache'][settingKey].push(value);
+        }
+        StringArrayValueHandler.writeSetting(settingKey)
+    };
+
+    var editorOptions = {};
+    editorOptions['title'] = PWM_SETTINGS['settings'][settingKey]['label'] + " - " + (iteration > -1 ? "Edit" : "Add") + " Value";
+    editorOptions['regex'] = PWM_SETTINGS['settings'][settingKey]['pattern'];
+    editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder'];
+    editorOptions['completeFunction'] = okAction;
+    editorOptions['value'] = iteration > -1 ? PWM_VAR['clientSettingCache'][settingKey][iteration] : '';
+
+    var isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'ldapDNsyntax');
+    if (isLdapDN) {
+        UILibrary.editLdapDN(okAction,{currentDN: editorOptions['value']});
+    } else {
+        UILibrary.stringEditorDialog(editorOptions);
+    }
+};
+
+StringArrayValueHandler.move = function(settingKey, moveUp, iteration) {
+    var currentValues = PWM_VAR['clientSettingCache'][settingKey];
+    if (moveUp) {
+        StringArrayValueHandler.arrayMoveUtil(currentValues, iteration, iteration - 1);
+    } else {
+        StringArrayValueHandler.arrayMoveUtil(currentValues, iteration, iteration + 1);
+    }
+    StringArrayValueHandler.writeSetting(settingKey)
+};
+
+StringArrayValueHandler.arrayMoveUtil = function(arr, fromIndex, toIndex) {
+    var element = arr[fromIndex];
+    arr.splice(fromIndex, 1);
+    arr.splice(toIndex, 0, element);
+};
+
+StringArrayValueHandler.removeValue = function(settingKey, iteration) {
+    var syntax = PWM_SETTINGS['settings'][settingKey]['syntax'];
+    var profileName = PWM_VAR['clientSettingCache'][settingKey][iteration];
+    var deleteFunction = function() {
+        var currentValues = PWM_VAR['clientSettingCache'][settingKey];
+        currentValues.splice(iteration,1);
+        StringArrayValueHandler.writeSetting(settingKey,false);
+    };
+    if (syntax === 'PROFILE') {
+        PWM_MAIN.showConfirmDialog({
+            text:PWM_CONFIG.showString('Confirm_RemoveProfile',{value1:profileName}),
+            okAction:function(){
+                deleteFunction();
+            }
+        });
+    } else if (syntax === 'DOMAINS') {
+        PWM_MAIN.showConfirmDialog({
+            text:PWM_CONFIG.showString('Confirm_RemoveDomain',{value1:profileName}),
+            okAction:function(){
+                deleteFunction();
+            }
+        });
+    } else {
+        deleteFunction();
+    }
+};
+
+StringArrayValueHandler.writeSetting = function(settingKey, reload) {
+    var syntax = PWM_SETTINGS['settings'][settingKey]['syntax'];
+    var nextFunction = function() {
+        if (syntax === 'PROFILE') {
+            PWM_MAIN.gotoUrl('editor');
+        }
+        if (reload) {
+            StringArrayValueHandler.init(settingKey);
+        } else {
+            StringArrayValueHandler.draw(settingKey);
+        }
+    };
+    var currentValues = PWM_VAR['clientSettingCache'][settingKey];
+    PWM_CFGEDIT.writeSetting(settingKey, currentValues, nextFunction);
+};

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

@@ -173,259 +173,6 @@ LocalizedStringValueHandler.addLocaleSetting = function(settingKey, localeKey) {
 };
 
 
-
-
-// -------------------------- string array value handler ------------------------------------
-
-var StringArrayValueHandler = {};
-
-StringArrayValueHandler.init = function(keyName) {
-    console.log('StringArrayValueHandler init for ' + keyName);
-
-    var parentDiv = 'table_setting_' + keyName;
-    PWM_MAIN.getObject(parentDiv).innerHTML = '<div id="tableTop_' + keyName + '">';
-    parentDiv = PWM_MAIN.getObject('tableTop_' + keyName);
-
-    PWM_VAR['clientSettingCache'][keyName + "_options"] = PWM_VAR['clientSettingCache'][keyName + "_options"] || {};
-    PWM_VAR['clientSettingCache'][keyName + "_options"]['parentDiv'] = parentDiv;
-    PWM_CFGEDIT.clearDivElements(parentDiv, true);
-    PWM_CFGEDIT.readSetting(keyName, function(resultValue) {
-        PWM_VAR['clientSettingCache'][keyName] = resultValue;
-        StringArrayValueHandler.draw(keyName);
-
-        var syntax = PWM_SETTINGS['settings'][keyName]['syntax'];
-        if (syntax === 'PROFILE') {
-            PWM_MAIN.getObject("resetButton-" + keyName).style.display = 'none';
-            PWM_MAIN.getObject("helpButton-" + keyName).style.display = 'none';
-            PWM_MAIN.getObject("modifiedNoticeIcon-" + keyName).style.display = 'none';
-        }
-    });
-};
-
-
-StringArrayValueHandler.draw = function(settingKey) {
-    var parentDiv = PWM_VAR['clientSettingCache'][settingKey + "_options"]['parentDiv'];
-    var parentDivElement = PWM_MAIN.getObject(parentDiv);
-
-    PWM_CFGEDIT.clearDivElements(parentDiv, false);
-    var resultValue = PWM_VAR['clientSettingCache'][settingKey];
-
-    var tableElement = document.createElement("table");
-    tableElement.setAttribute("style", "border-width: 0;");
-
-    var syntax = PWM_SETTINGS['settings'][settingKey]['syntax'];
-    if (syntax === 'PROFILE') {
-        var divDescriptionElement = document.createElement("div");
-        var text = PWM_SETTINGS['settings'][settingKey]['description'];
-        text += '<br/>' + PWM_CONFIG.showString('Display_ProfileNamingRules');
-        divDescriptionElement.innerHTML = text;
-        parentDivElement.appendChild(divDescriptionElement);
-
-        var defaultProfileRow = document.createElement("tr");
-        defaultProfileRow.setAttribute("colspan", "5");
-    }
-
-    var counter = 0;
-    var itemCount = PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]);
-    parentDivElement.appendChild(tableElement);
-
-    for (var i in resultValue) {
-        (function(iteration) {
-            StringArrayValueHandler.drawRow(settingKey, iteration, resultValue[iteration], itemCount, tableElement);
-            counter++;
-        })(i);
-    }
-
-    var settingProperties = PWM_SETTINGS['settings'][settingKey]['properties'];
-    if (settingProperties && 'Maximum' in settingProperties && itemCount >= settingProperties['Maximum']) {
-        // item count is already maxed out
-    } else {
-        var addItemButton = document.createElement("button");
-        addItemButton.setAttribute("type", "button");
-        addItemButton.setAttribute("class", "btn");
-        addItemButton.setAttribute("id", "button-" + settingKey + "-addItem");
-        addItemButton.innerHTML = '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>' + (syntax === 'PROFILE' ? "Add Profile" : "Add Value");
-        parentDivElement.appendChild(addItemButton);
-
-        PWM_MAIN.addEventHandler('button-' + settingKey + '-addItem', 'click', function () {
-            StringArrayValueHandler.valueHandler(settingKey, -1);
-        });
-    }
-};
-
-StringArrayValueHandler.drawRow = function(settingKey, iteration, value, itemCount, parentDivElement) {
-    var settingInfo = PWM_SETTINGS['settings'][settingKey];
-    var syntax = settingInfo['syntax'];
-
-    var inputID = 'value-' + settingKey + '-' + iteration;
-
-    var valueRow = document.createElement("tr");
-    valueRow.setAttribute("style", "border-width: 0");
-    valueRow.setAttribute("id",inputID + "_row");
-
-    var rowHtml = '';
-    if (syntax !== 'PROFILE') {
-        rowHtml = '<td id="button-' + inputID + '" style="border-width:0; width: 15px"><span class="pwm-icon pwm-icon-edit"/></td>';
-    }
-    rowHtml += '<td style=""><div class="configStringPanel" id="' + inputID + '"></div></td>';
-
-    if (syntax === 'PROFILE') {
-        var copyButtonID = 'button-' + settingKey + '-' + iteration + '-copy';
-        rowHtml += '<td class="noborder nopadding" style="width:10px" title="Copy">';
-        rowHtml += '<span id="' + copyButtonID + '" class="action-icon pwm-icon pwm-icon-copy"></span>';
-        rowHtml += '</td>';
-    }
-
-    var downButtonID = 'button-' + settingKey + '-' + iteration + '-moveDown';
-    rowHtml += '<td class="noborder nopadding" style="width:10px" title="Move Down">';
-    if (itemCount > 1 && iteration !== (itemCount -1)) {
-        rowHtml += '<span id="' + downButtonID + '" class="action-icon pwm-icon pwm-icon-chevron-down"></span>';
-    }
-    rowHtml += '</td>';
-
-    var upButtonID = 'button-' + settingKey + '-' + iteration + '-moveUp';
-    rowHtml += '<td class="noborder nopadding" style="width:10px" title="Move Up">';
-    if (itemCount > 1 && iteration !== 0) {
-        rowHtml += '<span id="' + upButtonID + '" class="action-icon pwm-icon pwm-icon-chevron-up"></span>';
-    }
-    rowHtml += '</td>';
-
-    var deleteButtonID = 'button-' + settingKey + '-' + iteration + '-delete';
-    rowHtml += '<td class="noborder nopadding" style="width:10px" title="Delete">';
-
-    if (itemCount > 1 || (!settingInfo['required'])) {
-        rowHtml += '<span id="' + deleteButtonID + '" class="delete-row-icon action-icon pwm-icon pwm-icon-times"></span>';
-    }
-    rowHtml += '</td>';
-
-
-    valueRow.innerHTML = rowHtml;
-    parentDivElement.appendChild(valueRow);
-
-    UILibrary.addTextValueToElement(inputID, value);
-    if (syntax !== 'PROFILE') {
-        PWM_MAIN.addEventHandler(inputID,'click',function(){
-            StringArrayValueHandler.valueHandler(settingKey,iteration);
-        });
-        PWM_MAIN.addEventHandler('button-' + inputID,'click',function(){
-            StringArrayValueHandler.valueHandler(settingKey,iteration);
-        });
-    } else {
-        PWM_MAIN.addEventHandler(copyButtonID,'click',function(){
-            var editorOptions = {};
-            editorOptions['title'] = 'Copy Profile - New Profile ID';
-            editorOptions['regex'] = PWM_SETTINGS['settings'][settingKey]['pattern'];
-            editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder'];
-            editorOptions['completeFunction'] = function(newValue){
-                var options = {};
-                options['setting'] = settingKey;
-                options['sourceID'] = value;
-                options['destinationID'] = newValue;
-                var resultFunction = function(data){
-                    if (data['error']) {
-                        PWM_MAIN.showErrorDialog(data);
-                    } else {
-                        PWM_MAIN.gotoUrl('editor');
-                    }
-                };
-                PWM_MAIN.showWaitDialog({loadFunction:function(){
-                        PWM_MAIN.ajaxRequest("editor?processAction=copyProfile",resultFunction,{content:options});
-                    }});
-            };
-            UILibrary.stringEditorDialog(editorOptions);
-        });
-    }
-
-    if (itemCount > 1 && iteration !== (itemCount -1)) {
-        PWM_MAIN.addEventHandler(downButtonID,'click',function(){StringArrayValueHandler.move(settingKey,false,iteration)});
-    }
-
-    if (itemCount > 1 && iteration !== 0) {
-        PWM_MAIN.addEventHandler(upButtonID,'click',function(){StringArrayValueHandler.move(settingKey,true,iteration)});
-    }
-
-    if (itemCount > 1 || !PWM_SETTINGS['settings'][settingKey]['required']) {
-        PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){StringArrayValueHandler.removeValue(settingKey,iteration)});
-    }
-};
-
-StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
-    var okAction = function(value) {
-        if (iteration > -1) {
-            PWM_VAR['clientSettingCache'][settingKey][iteration] = value;
-        } else {
-            PWM_VAR['clientSettingCache'][settingKey].push(value);
-        }
-        StringArrayValueHandler.writeSetting(settingKey)
-    };
-
-    var editorOptions = {};
-    editorOptions['title'] = PWM_SETTINGS['settings'][settingKey]['label'] + " - " + (iteration > -1 ? "Edit" : "Add") + " Value";
-    editorOptions['regex'] = PWM_SETTINGS['settings'][settingKey]['pattern'];
-    editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder'];
-    editorOptions['completeFunction'] = okAction;
-    editorOptions['value'] = iteration > -1 ? PWM_VAR['clientSettingCache'][settingKey][iteration] : '';
-
-    var isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'ldapDNsyntax');
-    if (isLdapDN) {
-        UILibrary.editLdapDN(okAction,{currentDN: editorOptions['value']});
-    } else {
-        UILibrary.stringEditorDialog(editorOptions);
-    }
-};
-
-StringArrayValueHandler.move = function(settingKey, moveUp, iteration) {
-    var currentValues = PWM_VAR['clientSettingCache'][settingKey];
-    if (moveUp) {
-        StringArrayValueHandler.arrayMoveUtil(currentValues, iteration, iteration - 1);
-    } else {
-        StringArrayValueHandler.arrayMoveUtil(currentValues, iteration, iteration + 1);
-    }
-    StringArrayValueHandler.writeSetting(settingKey)
-};
-
-StringArrayValueHandler.arrayMoveUtil = function(arr, fromIndex, toIndex) {
-    var element = arr[fromIndex];
-    arr.splice(fromIndex, 1);
-    arr.splice(toIndex, 0, element);
-};
-
-StringArrayValueHandler.removeValue = function(settingKey, iteration) {
-    var syntax = PWM_SETTINGS['settings'][settingKey]['syntax'];
-    var profileName = PWM_VAR['clientSettingCache'][settingKey][iteration];
-    var deleteFunction = function() {
-        var currentValues = PWM_VAR['clientSettingCache'][settingKey];
-        currentValues.splice(iteration,1);
-        StringArrayValueHandler.writeSetting(settingKey,false);
-    };
-    if (syntax === 'PROFILE') {
-        PWM_MAIN.showConfirmDialog({
-            text:PWM_CONFIG.showString('Confirm_RemoveProfile',{value1:profileName}),
-            okAction:function(){
-                deleteFunction();
-            }
-        });
-    } else {
-        deleteFunction();
-    }
-};
-
-StringArrayValueHandler.writeSetting = function(settingKey, reload) {
-    var syntax = PWM_SETTINGS['settings'][settingKey]['syntax'];
-    var nextFunction = function() {
-        if (syntax === 'PROFILE') {
-            PWM_MAIN.gotoUrl('editor');
-        }
-        if (reload) {
-            StringArrayValueHandler.init(settingKey);
-        } else {
-            StringArrayValueHandler.draw(settingKey);
-        }
-    };
-    var currentValues = PWM_VAR['clientSettingCache'][settingKey];
-    PWM_CFGEDIT.writeSetting(settingKey, currentValues, nextFunction);
-};
-
 // -------------------------- multi locale table handler ------------------------------------
 
 var MultiLocaleTableHandler = {};

+ 3 - 0
webapp/src/main/webapp/public/resources/js/configeditor.js

@@ -81,6 +81,9 @@ PWM_CFGEDIT.syntaxFunctionMap = {
     PROFILE: function (settingKey) {
         StringArrayValueHandler.init(settingKey);
     },
+    DOMAINS: function (settingKey) {
+        StringArrayValueHandler.init(settingKey);
+    },
     LOCALIZED_STRING: function (settingKey) {
         LocalizedStringValueHandler.init(settingKey);
     },