Browse Source

introduce domain properties

Jason Rivard 2 years ago
parent
commit
bfb22da071
39 changed files with 497 additions and 290 deletions
  1. 0 41
      server/src/main/java/password/pwm/AppProperty.java
  2. 106 0
      server/src/main/java/password/pwm/DomainProperty.java
  3. 42 2
      server/src/main/java/password/pwm/config/DomainConfig.java
  4. 4 4
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  5. 8 8
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  6. 1 1
      server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java
  7. 0 22
      server/src/main/java/password/pwm/http/PwmHttpResponseWrapper.java
  8. 2 1
      server/src/main/java/password/pwm/http/PwmRequestLocaleResolver.java
  9. 8 6
      server/src/main/java/password/pwm/http/PwmResponse.java
  10. 4 4
      server/src/main/java/password/pwm/http/PwmSession.java
  11. 3 3
      server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java
  12. 45 66
      server/src/main/java/password/pwm/http/filter/CookieManagementFilter.java
  13. 2 1
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  14. 3 2
      server/src/main/java/password/pwm/http/filter/SessionFilter.java
  15. 3 2
      server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java
  16. 2 2
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  17. 14 7
      server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java
  18. 15 9
      server/src/main/java/password/pwm/ldap/LdapBrowser.java
  19. 3 3
      server/src/main/java/password/pwm/ldap/LdapDomainService.java
  20. 2 2
      server/src/main/java/password/pwm/ldap/LdapGuidReaderUtil.java
  21. 9 8
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  22. 3 3
      server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java
  23. 2 1
      server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java
  24. 5 5
      server/src/main/java/password/pwm/ldap/search/UserSearchService.java
  25. 4 3
      server/src/main/java/password/pwm/util/CaptchaUtility.java
  26. 2 1
      server/src/main/java/password/pwm/util/Validator.java
  27. 3 2
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  28. 2 2
      server/src/main/java/password/pwm/util/secure/BeanCryptoMachine.java
  29. 1 42
      server/src/main/resources/password/pwm/AppProperty.properties
  30. 65 0
      server/src/main/resources/password/pwm/DomainProperty.properties
  31. 1 1
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  32. 24 0
      server/src/test/java/password/pwm/AppPropertyTest.java
  33. 5 9
      server/src/test/java/password/pwm/config/PwmSettingTest.java
  34. 7 13
      server/src/test/java/password/pwm/error/PwmErrorTest.java
  35. 6 10
      server/src/test/java/password/pwm/health/HealthMessageTest.java
  36. 29 0
      server/src/test/java/password/pwm/http/ServletTest.java
  37. 21 0
      server/src/test/java/password/pwm/http/servlet/ControlledPwmServletTest.java
  38. 37 0
      server/src/test/java/password/pwm/util/localdb/TestHelper.java
  39. 4 4
      webapp/src/main/webapp/WEB-INF/web.xml

+ 0 - 41
server/src/main/java/password/pwm/AppProperty.java

@@ -60,8 +60,6 @@ public enum AppProperty
     CACHE_FORM_UNIQUE_VALUE_LIFETIME_MS             ( "cache.uniqueFormValueLifetimeMS" ),
     CACHE_FORM_UNIQUE_VALUE_LIFETIME_MS             ( "cache.uniqueFormValueLifetimeMS" ),
     CLIENT_ACTIVITY_MAX_EPS_RATE                    ( "client.ajax.activityMaxEpsRate" ),
     CLIENT_ACTIVITY_MAX_EPS_RATE                    ( "client.ajax.activityMaxEpsRate" ),
     CLIENT_AJAX_PW_WAIT_CHECK_SECONDS               ( "client.ajax.changePasswordWaitCheckSeconds" ),
     CLIENT_AJAX_PW_WAIT_CHECK_SECONDS               ( "client.ajax.changePasswordWaitCheckSeconds" ),
-    CLIENT_AJAX_TYPING_TIMEOUT                      ( "client.ajax.typingTimeout" ),
-    CLIENT_AJAX_TYPING_WAIT                         ( "client.ajax.typingWait" ),
     CLIENT_FORM_NONCE_ENABLE                        ( "client.formNonce.enable" ),
     CLIENT_FORM_NONCE_ENABLE                        ( "client.formNonce.enable" ),
     CLIENT_FORM_NONCE_LENGTH                        ( "client.formNonce.length" ),
     CLIENT_FORM_NONCE_LENGTH                        ( "client.formNonce.length" ),
     CLIENT_FORM_CLIENT_REGEX_ENABLED                ( "client.form.clientRegexEnable" ),
     CLIENT_FORM_CLIENT_REGEX_ENABLED                ( "client.form.clientRegexEnable" ),
@@ -112,20 +110,6 @@ public enum AppProperty
     HTTP_RESOURCES_ENABLE_PATH_NONCE                ( "http.resources.pathNonceEnable" ),
     HTTP_RESOURCES_ENABLE_PATH_NONCE                ( "http.resources.pathNonceEnable" ),
     HTTP_RESOURCES_NONCE_PATH_PREFIX                ( "http.resources.pathNoncePrefix" ),
     HTTP_RESOURCES_NONCE_PATH_PREFIX                ( "http.resources.pathNoncePrefix" ),
     HTTP_RESOURCES_ZIP_FILES                        ( "http.resources.zipFiles" ),
     HTTP_RESOURCES_ZIP_FILES                        ( "http.resources.zipFiles" ),
-    HTTP_COOKIE_DEFAULT_SECURE_FLAG                 ( "http.cookie.default.secureFlag" ),
-    HTTP_COOKIE_HTTPONLY_ENABLE                     ( "http.cookie.httponly.enable" ),
-    HTTP_COOKIE_THEME_NAME                          ( "http.cookie.theme.name" ),
-    HTTP_COOKIE_THEME_AGE                           ( "http.cookie.theme.age" ),
-    HTTP_COOKIE_LOCALE_NAME                         ( "http.cookie.locale.name" ),
-    HTTP_COOKIE_AUTHRECORD_NAME                     ( "http.cookie.authRecord.name" ),
-    HTTP_COOKIE_AUTHRECORD_AGE                      ( "http.cookie.authRecord.age" ),
-    HTTP_COOKIE_MAX_READ_LENGTH                     ( "http.cookie.maxReadLength" ),
-    HTTP_COOKIE_CAPTCHA_SKIP_NAME                   ( "http.cookie.captchaSkip.name" ),
-    HTTP_COOKIE_CAPTCHA_SKIP_AGE                    ( "http.cookie.captchaSkip.age" ),
-    HTTP_COOKIE_LOGIN_NAME                          ( "http.cookie.login.name" ),
-    HTTP_COOKIE_NONCE_NAME                          ( "http.cookie.nonce.name" ),
-    HTTP_COOKIE_NONCE_LENGTH                        ( "http.cookie.nonce.length" ),
-    HTTP_COOKIE_SAMESITE_VALUE                      ( "http.cookie.sameSite.value" ),
     HTTP_BASIC_AUTH_CHARSET                         ( "http.basicAuth.charset" ),
     HTTP_BASIC_AUTH_CHARSET                         ( "http.basicAuth.charset" ),
     HTTP_BODY_MAXREAD_LENGTH                        ( "http.body.maxReadLength" ),
     HTTP_BODY_MAXREAD_LENGTH                        ( "http.body.maxReadLength" ),
     HTTP_CLIENT_ALWAYS_LOG_ENTITIES                 ( "http.client.alwaysLogEntities" ),
     HTTP_CLIENT_ALWAYS_LOG_ENTITIES                 ( "http.client.alwaysLogEntities" ),
@@ -218,31 +202,6 @@ public enum AppProperty
     HELPDESK_TOKEN_VALUE                            ( "helpdesk.token.value" ),
     HELPDESK_TOKEN_VALUE                            ( "helpdesk.token.value" ),
     HELPDESK_VERIFICATION_INVALID_DELAY_MS          ( "helpdesk.verification.invalid.delayMs" ),
     HELPDESK_VERIFICATION_INVALID_DELAY_MS          ( "helpdesk.verification.invalid.delayMs" ),
     HELPDESK_VERIFICATION_TIMEOUT_SECONDS           ( "helpdesk.verification.timeoutSeconds" ),
     HELPDESK_VERIFICATION_TIMEOUT_SECONDS           ( "helpdesk.verification.timeoutSeconds" ),
-    LDAP_RESOLVE_CANONICAL_DN                       ( "ldap.resolveCanonicalDN" ),
-    LDAP_CACHE_CANONICAL_ENABLE                     ( "ldap.cache.canonical.enable" ),
-    LDAP_CACHE_CANONICAL_SECONDS                    ( "ldap.cache.canonical.seconds" ),
-    LDAP_CACHE_USER_GUID_ENABLE                     ( "ldap.cache.userGuid.enable" ),
-    LDAP_CACHE_USER_GUID_SECONDS                    ( "ldap.cache.userGuid.seconds" ),
-    LDAP_CHAI_SETTINGS                              ( "ldap.chaiSettings" ),
-    LDAP_PROXY_CONNECTION_PER_PROFILE               ( "ldap.proxy.connectionsPerProfile" ),
-    LDAP_PROXY_MAX_CONNECTIONS                      ( "ldap.proxy.maxConnections" ),
-    LDAP_PROXY_IDLE_THREAD_LOCAL_TIMEOUT_MS         ( "ldap.proxy.idleThreadLocal.timeoutMS" ),
-    LDAP_EXTENSIONS_NMAS_ENABLE                     ( "ldap.extensions.nmas.enable" ),
-    LDAP_CONNECTION_TIMEOUT                         ( "ldap.connection.timeoutMS" ),
-    LDAP_PROFILE_RETRY_DELAY                        ( "ldap.profile.retryDelayMS" ),
-    LDAP_PROMISCUOUS_ENABLE                         ( "ldap.promiscuousEnable" ),
-    LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS       ( "ldap.password.replicaCheck.initialDelayMS" ),
-    LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS      ( "ldap.password.replicaCheck.cycleDelayMS" ),
-    LDAP_PASSWORD_CHANGE_SELF_ENABLE                ( "ldap.password.change.self.enable" ),
-    LDAP_PASSWORD_CHANGE_HELPDESK_ENABLE            ( "ldap.password.change.helpdesk.enable" ),
-    LDAP_GUID_PATTERN                               ( "ldap.guid.pattern" ),
-    LDAP_BROWSER_MAX_ENTRIES                        ( "ldap.browser.maxEntries" ),
-    LDAP_SEARCH_PAGING_ENABLE                       ( "ldap.search.paging.enable" ),
-    LDAP_SEARCH_PAGING_SIZE                         ( "ldap.search.paging.size" ),
-    LDAP_SEARCH_PARALLEL_ENABLE                     ( "ldap.search.parallel.enable" ),
-    LDAP_SEARCH_PARALLEL_FACTOR                     ( "ldap.search.parallel.factor" ),
-    LDAP_SEARCH_PARALLEL_THREAD_MAX                 ( "ldap.search.parallel.threadMax" ),
-    LDAP_ORACLE_POST_TEMPPW_USE_CURRENT_TIME        ( "ldap.oracle.postTempPasswordUseCurrentTime" ),
     LOGGING_OUTPUT_CONFIGURATION                    ( "logging.output.configuration.enable" ),
     LOGGING_OUTPUT_CONFIGURATION                    ( "logging.output.configuration.enable" ),
     LOGGING_OUTPUT_HEALTHCHECK                      ( "logging.output.healthCheck.enable" ),
     LOGGING_OUTPUT_HEALTHCHECK                      ( "logging.output.healthCheck.enable" ),
     LOGGING_OUTPUT_RUNTIME                          ( "logging.output.runtime.enable" ),
     LOGGING_OUTPUT_RUNTIME                          ( "logging.output.runtime.enable" ),

+ 106 - 0
server/src/main/java/password/pwm/DomainProperty.java

@@ -0,0 +1,106 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm;
+
+import password.pwm.util.java.EnumUtil;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.ResourceBundle;
+
+public enum DomainProperty
+{
+    CLIENT_AJAX_TYPING_TIMEOUT                      ( "client.ajax.typingTimeout" ),
+    CLIENT_AJAX_TYPING_WAIT                         ( "client.ajax.typingWait" ),
+    HTTP_COOKIE_DEFAULT_SECURE_FLAG                 ( "http.cookie.default.secureFlag" ),
+    HTTP_COOKIE_HTTPONLY_ENABLE                     ( "http.cookie.httponly.enable" ),
+    HTTP_COOKIE_THEME_NAME                          ( "http.cookie.theme.name" ),
+    HTTP_COOKIE_THEME_AGE                           ( "http.cookie.theme.age" ),
+    HTTP_COOKIE_LOCALE_NAME                         ( "http.cookie.locale.name" ),
+    HTTP_COOKIE_AUTHRECORD_NAME                     ( "http.cookie.authRecord.name" ),
+    HTTP_COOKIE_AUTHRECORD_AGE                      ( "http.cookie.authRecord.age" ),
+    HTTP_COOKIE_MAX_READ_LENGTH                     ( "http.cookie.maxReadLength" ),
+    HTTP_COOKIE_CAPTCHA_SKIP_NAME                   ( "http.cookie.captchaSkip.name" ),
+    HTTP_COOKIE_CAPTCHA_SKIP_AGE                    ( "http.cookie.captchaSkip.age" ),
+    HTTP_COOKIE_LOGIN_NAME                          ( "http.cookie.login.name" ),
+    HTTP_COOKIE_NONCE_NAME                          ( "http.cookie.nonce.name" ),
+    HTTP_COOKIE_NONCE_LENGTH                        ( "http.cookie.nonce.length" ),
+    HTTP_COOKIE_SAMESITE_VALUE                      ( "http.cookie.sameSite.value" ),
+    LDAP_RESOLVE_CANONICAL_DN                       ( "ldap.resolveCanonicalDN" ),
+    LDAP_CACHE_CANONICAL_ENABLE                     ( "ldap.cache.canonical.enable" ),
+    LDAP_CACHE_CANONICAL_SECONDS                    ( "ldap.cache.canonical.seconds" ),
+    LDAP_CACHE_USER_GUID_ENABLE                     ( "ldap.cache.userGuid.enable" ),
+    LDAP_CACHE_USER_GUID_SECONDS                    ( "ldap.cache.userGuid.seconds" ),
+    LDAP_CHAI_SETTINGS                              ( "ldap.chaiSettings" ),
+    LDAP_PROXY_CONNECTION_PER_PROFILE               ( "ldap.proxy.connectionsPerProfile" ),
+    LDAP_PROXY_MAX_CONNECTIONS                      ( "ldap.proxy.maxConnections" ),
+    LDAP_PROXY_IDLE_THREAD_LOCAL_TIMEOUT_MS         ( "ldap.proxy.idleThreadLocal.timeoutMS" ),
+    LDAP_EXTENSIONS_NMAS_ENABLE                     ( "ldap.extensions.nmas.enable" ),
+    LDAP_CONNECTION_TIMEOUT                         ( "ldap.connection.timeoutMS" ),
+    LDAP_PROFILE_RETRY_DELAY                        ( "ldap.profile.retryDelayMS" ),
+    LDAP_PROMISCUOUS_ENABLE                         ( "ldap.promiscuousEnable" ),
+    LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS       ( "ldap.password.replicaCheck.initialDelayMS" ),
+    LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS      ( "ldap.password.replicaCheck.cycleDelayMS" ),
+    LDAP_PASSWORD_CHANGE_SELF_ENABLE                ( "ldap.password.change.self.enable" ),
+    LDAP_PASSWORD_CHANGE_HELPDESK_ENABLE            ( "ldap.password.change.helpdesk.enable" ),
+    LDAP_GUID_PATTERN                               ( "ldap.guid.pattern" ),
+    LDAP_BROWSER_MAX_ENTRIES                        ( "ldap.browser.maxEntries" ),
+    LDAP_SEARCH_PAGING_ENABLE                       ( "ldap.search.paging.enable" ),
+    LDAP_SEARCH_PAGING_SIZE                         ( "ldap.search.paging.size" ),
+    LDAP_SEARCH_PARALLEL_ENABLE                     ( "ldap.search.parallel.enable" ),
+    LDAP_SEARCH_PARALLEL_FACTOR                     ( "ldap.search.parallel.factor" ),
+    LDAP_SEARCH_PARALLEL_THREAD_MAX                 ( "ldap.search.parallel.threadMax" ),
+    LDAP_ORACLE_POST_TEMPPW_USE_CURRENT_TIME        ( "ldap.oracle.postTempPasswordUseCurrentTime" ),;
+
+    private final String key;
+    private final String defaultValue;
+
+    DomainProperty( final String key )
+    {
+        this.key = key;
+        this.defaultValue = readDomainPropertiesBundle( key );
+    }
+
+    public String getKey( )
+    {
+        return key;
+    }
+
+    public String getDefaultValue( )
+    {
+        return defaultValue;
+    }
+
+    public boolean isDefaultValue( final String value )
+    {
+        return Objects.equals( defaultValue, value );
+    }
+
+    public static Optional<DomainProperty> forKey( final String key )
+    {
+        return EnumUtil.readEnumFromPredicate( DomainProperty.class, domainProperty -> Objects.equals( domainProperty.getKey(), key ) );
+    }
+
+    private static String readDomainPropertiesBundle( final String key )
+    {
+        return ResourceBundle.getBundle( DomainProperty.class.getName() ).getString( key );
+    }
+}

+ 42 - 2
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -21,6 +21,7 @@
 package password.pwm.config;
 package password.pwm.config;
 
 
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
 import password.pwm.bean.PrivateKeyCertificate;
@@ -56,11 +57,12 @@ import password.pwm.util.PasswordData;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.EnumUtil;
 import password.pwm.util.java.EnumUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 
 
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
@@ -85,6 +87,8 @@ public class DomainConfig implements SettingReader
     private final StoredSettingReader settingReader;
     private final StoredSettingReader settingReader;
     private final PwmSecurityKey domainSecurityKey;
     private final PwmSecurityKey domainSecurityKey;
 
 
+    private final Map<DomainProperty, String> domainProperties;
+
     public DomainConfig( final AppConfig appConfig, final DomainID domainID )
     public DomainConfig( final AppConfig appConfig, final DomainID domainID )
     {
     {
         this.appConfig = Objects.requireNonNull( appConfig );
         this.appConfig = Objects.requireNonNull( appConfig );
@@ -92,6 +96,8 @@ public class DomainConfig implements SettingReader
         this.domainID = Objects.requireNonNull( domainID );
         this.domainID = Objects.requireNonNull( domainID );
         this.settingReader = new StoredSettingReader( storedConfiguration, null, domainID );
         this.settingReader = new StoredSettingReader( storedConfiguration, null, domainID );
 
 
+        this.domainProperties = makeDomainPropertyOverrides( domainID, appConfig );
+
         this.cachedPasswordPolicy = getPasswordProfileIDs().stream()
         this.cachedPasswordPolicy = getPasswordProfileIDs().stream()
                 .map( profile -> PwmPasswordPolicy.createPwmPasswordPolicy( this, profile ) )
                 .map( profile -> PwmPasswordPolicy.createPwmPasswordPolicy( this, profile ) )
                 .collect( CollectorUtil.toUnmodifiableLinkedMap(
                 .collect( CollectorUtil.toUnmodifiableLinkedMap(
@@ -283,6 +289,11 @@ public class DomainConfig implements SettingReader
         return appConfig.readAppProperty( property );
         return appConfig.readAppProperty( property );
     }
     }
 
 
+    public String readDomainProperty( final DomainProperty property )
+    {
+        return domainProperties.getOrDefault( property, property.getDefaultValue() );
+    }
+
     public DomainID getDomainID()
     public DomainID getDomainID()
     {
     {
         return domainID;
         return domainID;
@@ -395,7 +406,7 @@ public class DomainConfig implements SettingReader
         {
         {
             methods.add( DataStorageMethod.NMAS );
             methods.add( DataStorageMethod.NMAS );
         }
         }
-        return Collections.unmodifiableList( methods );
+        return List.copyOf( methods );
     }
     }
 
 
 
 
@@ -427,6 +438,35 @@ public class DomainConfig implements SettingReader
         }
         }
     }
     }
 
 
+    private static Map<DomainProperty, String> makeDomainPropertyOverrides( final DomainID domainID, final AppConfig appConfig )
+    {
+        final List<String> settingValues = appConfig.readSettingAsStringArray( PwmSetting.APP_PROPERTY_OVERRIDES );
+
+        final Map<String, String> stringMap = StringUtil.convertStringListToNameValuePair( settingValues, "=" );
+
+        final Map<DomainProperty, String> appPropertyMap = new EnumMap<>( DomainProperty.class );
+        for ( final Map.Entry<String, String> stringEntry : stringMap.entrySet() )
+        {
+            final String value = stringEntry.getValue();
+            final String domainPrefixedPropKey = domainID.stringValue() + "." + stringEntry.getKey();
+
+            final Optional<DomainProperty> domainProperty = DomainProperty.forKey( domainPrefixedPropKey )
+                    .or( () -> DomainProperty.forKey( stringEntry.getKey() ) );
+
+            domainProperty
+                    .ifPresent( domainPropertyKey ->
+                    {
+                        if ( !domainPropertyKey.isDefaultValue( value ) )
+                        {
+                            appPropertyMap.put( domainPropertyKey, value );
+                        }
+                    } );
+        }
+
+        return CollectionUtil.unmodifiableEnumMap( appPropertyMap, DomainProperty.class );
+    }
+
+
     @Override
     @Override
     public String getValueHash()
     public String getValueHash()
     {
     {

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

@@ -24,7 +24,7 @@ import com.novell.ldapchai.ChaiEntry;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProvider;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.ProfileID;
 import password.pwm.bean.ProfileID;
@@ -168,14 +168,14 @@ public class LdapProfile extends AbstractProfile implements Profile
         final Instant startTime = Instant.now();
         final Instant startTime = Instant.now();
 
 
         {
         {
-            final boolean doCanonicalDnResolve = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_RESOLVE_CANONICAL_DN ) );
+            final boolean doCanonicalDnResolve = Boolean.parseBoolean( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_RESOLVE_CANONICAL_DN ) );
             if ( !doCanonicalDnResolve )
             if ( !doCanonicalDnResolve )
             {
             {
                 return dnValue;
                 return dnValue;
             }
             }
         }
         }
 
 
-        final boolean enableCanonicalCache = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_CACHE_CANONICAL_ENABLE ) );
+        final boolean enableCanonicalCache = Boolean.parseBoolean( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_CACHE_CANONICAL_ENABLE ) );
 
 
         String canonicalValue = null;
         String canonicalValue = null;
         final CacheKey cacheKey = CacheKey.newKey( LdapProfile.class, null, "canonicalDN-" + this.getId() + "-" + dnValue );
         final CacheKey cacheKey = CacheKey.newKey( LdapProfile.class, null, "canonicalDN-" + this.getId() + "-" + dnValue );
@@ -198,7 +198,7 @@ public class LdapProfile extends AbstractProfile implements Profile
 
 
                 if ( enableCanonicalCache )
                 if ( enableCanonicalCache )
                 {
                 {
-                    final long cacheSeconds = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_CACHE_CANONICAL_SECONDS ) );
+                    final long cacheSeconds = Long.parseLong( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_CACHE_CANONICAL_SECONDS ) );
                     final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration( TimeDuration.of( cacheSeconds, TimeDuration.Unit.SECONDS ) );
                     final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration( TimeDuration.of( cacheSeconds, TimeDuration.Unit.SECONDS ) );
                     pwmDomain.getCacheService().put( cacheKey, cachePolicy, canonicalValue );
                     pwmDomain.getCacheService().put( cacheKey, cachePolicy, canonicalValue );
                 }
                 }

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

@@ -21,7 +21,7 @@
 package password.pwm.health;
 package password.pwm.health;
 
 
 import lombok.Value;
 import lombok.Value;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
@@ -91,10 +91,10 @@ public class ConfigurationChecker implements HealthSupplier
             VerifyIfDeprecatedJsFormOptionUsed.class,
             VerifyIfDeprecatedJsFormOptionUsed.class,
             VerifyNewUserLdapProfile.class,
             VerifyNewUserLdapProfile.class,
             VerifyPasswordWaitTimes.class,
             VerifyPasswordWaitTimes.class,
+            VerifyBasicSystemConfigs.class,
             VerifyUserPermissionSettings.class );
             VerifyUserPermissionSettings.class );
 
 
     private static final List<Class<? extends ConfigSystemHealthCheck>> SYSTEM_CHECKS = List.of(
     private static final List<Class<? extends ConfigSystemHealthCheck>> SYSTEM_CHECKS = List.of(
-            VerifyBasicSystemConfigs.class,
             VerifyDbConfiguredIfNeededSystem.class );
             VerifyDbConfiguredIfNeededSystem.class );
 
 
     @Override
     @Override
@@ -214,20 +214,20 @@ public class ConfigurationChecker implements HealthSupplier
         }
         }
     }
     }
 
 
-    static class VerifyBasicSystemConfigs implements ConfigSystemHealthCheck
+    static class VerifyBasicSystemConfigs implements ConfigDomainHealthCheck
     {
     {
         @Override
         @Override
-        public List<HealthRecord> healthCheck( final SystemHealthCheckRequest systemHealthCheckRequest )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
         {
-            final AppConfig config = systemHealthCheckRequest.getDomainConfig();
-            final Locale locale = systemHealthCheckRequest.getLocale();
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
 
 
             final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
             final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
             final List<HealthRecord> records = new ArrayList<>();
             final List<HealthRecord> records = new ArrayList<>();
 
 
-            if ( Boolean.parseBoolean( config.readAppProperty( AppProperty.LDAP_PROMISCUOUS_ENABLE ) ) )
+            if ( Boolean.parseBoolean( config.readDomainProperty( DomainProperty.LDAP_PROMISCUOUS_ENABLE ) ) )
             {
             {
-                final String appPropertyKey = "AppProperty" + separator + AppProperty.LDAP_PROMISCUOUS_ENABLE.getKey();
+                final String appPropertyKey = "AppProperty" + separator + DomainProperty.LDAP_PROMISCUOUS_ENABLE.getKey();
                 records.add( HealthRecord.forMessage(
                 records.add( HealthRecord.forMessage(
                         DomainID.systemId(),
                         DomainID.systemId(),
                         HealthMessage.Config_PromiscuousLDAP,
                         HealthMessage.Config_PromiscuousLDAP,

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

@@ -388,7 +388,7 @@ public class PwmHttpRequestWrapper
 
 
         return CollectionUtil.iteratorToStream( getHttpServletRequest().getParameterNames().asIterator() )
         return CollectionUtil.iteratorToStream( getHttpServletRequest().getParameterNames().asIterator() )
                 .map( s -> Validator.sanitizeInputValue( appConfig, s, maxChars ) )
                 .map( s -> Validator.sanitizeInputValue( appConfig, s, maxChars ) )
-                .collect( Collectors.toUnmodifiableList() );
+                .toList();
 
 
     }
     }
 
 

+ 0 - 22
server/src/main/java/password/pwm/http/PwmHttpResponseWrapper.java

@@ -20,11 +20,7 @@
 
 
 package password.pwm.http;
 package password.pwm.http;
 
 
-import password.pwm.AppProperty;
-import password.pwm.PwmDomain;
 import password.pwm.config.AppConfig;
 import password.pwm.config.AppConfig;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.filter.CookieManagementFilter;
 import password.pwm.util.Validator;
 import password.pwm.util.Validator;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
@@ -98,22 +94,4 @@ public class PwmHttpResponseWrapper
     {
     {
         return this.getHttpServletResponse().getOutputStream();
         return this.getHttpServletResponse().getOutputStream();
     }
     }
-
-
-    void addSameSiteCookieAttribute( )
-    {
-        final PwmDomain pwmDomain;
-        try
-        {
-            pwmDomain = PwmRequest.forRequest( this.httpServletRequest, this.httpServletResponse ).getPwmDomain();
-            final String value = pwmDomain.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_SAMESITE_VALUE );
-            CookieManagementFilter.addSameSiteCookieAttribute( httpServletResponse, value );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.trace( () -> "unable to load application configuration while checking samesite cookie attribute config", e );
-        }
-    }
-
-
 }
 }

+ 2 - 1
server/src/main/java/password/pwm/http/PwmRequestLocaleResolver.java

@@ -22,6 +22,7 @@ package password.pwm.http;
 
 
 import lombok.Value;
 import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.AppConfig;
 import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.DomainConfig;
@@ -88,7 +89,7 @@ class PwmRequestLocaleResolver
 
 
     private static String readLocaleCookieName( final PwmRequest pwmRequest )
     private static String readLocaleCookieName( final PwmRequest pwmRequest )
     {
     {
-        return pwmRequest.getAppConfig().readAppProperty( AppProperty.HTTP_COOKIE_LOCALE_NAME );
+        return pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_LOCALE_NAME );
     }
     }
 
 
     private static class RequestParamReader implements RequestLocaleReader
     private static class RequestParamReader implements RequestLocaleReader

+ 8 - 6
server/src/main/java/password/pwm/http/PwmResponse.java

@@ -22,13 +22,15 @@ package password.pwm.http;
 
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.config.AppConfig;
 import password.pwm.config.AppConfig;
+import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.filter.CookieManagementFilter;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.command.CommandServlet;
 import password.pwm.http.servlet.command.CommandServlet;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
@@ -345,11 +347,11 @@ public class PwmResponse extends PwmHttpResponseWrapper
             LOGGER.warn( () -> "attempt to write cookie '" + cookieName + "' after response is committed" );
             LOGGER.warn( () -> "attempt to write cookie '" + cookieName + "' after response is committed" );
         }
         }
 
 
-        final AppConfig appConfig = pwmRequest.getAppConfig();
+        final DomainConfig domainConfig = pwmRequest.getDomainConfig();
 
 
         final boolean secureFlag;
         final boolean secureFlag;
         {
         {
-            final String configValue = appConfig.readAppProperty( AppProperty.HTTP_COOKIE_DEFAULT_SECURE_FLAG );
+            final String configValue = domainConfig.readDomainProperty( DomainProperty.HTTP_COOKIE_DEFAULT_SECURE_FLAG );
             if ( configValue == null || "auto".equalsIgnoreCase( configValue ) )
             if ( configValue == null || "auto".equalsIgnoreCase( configValue ) )
             {
             {
                 secureFlag = pwmRequest.getHttpServletRequest().isSecure();
                 secureFlag = pwmRequest.getHttpServletRequest().isSecure();
@@ -360,7 +362,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
             }
             }
         }
         }
 
 
-        final boolean httpOnlyEnabled = Boolean.parseBoolean( appConfig.readAppProperty( AppProperty.HTTP_COOKIE_HTTPONLY_ENABLE ) );
+        final boolean httpOnlyEnabled = Boolean.parseBoolean( domainConfig.readDomainProperty( DomainProperty.HTTP_COOKIE_HTTPONLY_ENABLE ) );
         final boolean httpOnly = httpOnlyEnabled && !EnumUtil.enumArrayContainsValue( flags, PwmHttpResponseWrapper.Flag.NonHttpOnly );
         final boolean httpOnly = httpOnlyEnabled && !EnumUtil.enumArrayContainsValue( flags, PwmHttpResponseWrapper.Flag.NonHttpOnly );
 
 
         final String value;
         final String value;
@@ -378,7 +380,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
                 else
                 else
                 {
                 {
                     value = StringUtil.urlEncode(
                     value = StringUtil.urlEncode(
-                            Validator.sanitizeHeaderValue( appConfig, cookieValue )
+                            Validator.sanitizeHeaderValue( domainConfig, cookieValue )
                     );
                     );
                 }
                 }
             }
             }
@@ -397,7 +399,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
             LOGGER.warn( () -> "writing large cookie to response: cookieName=" + cookieName + ", length=" + value.length() );
             LOGGER.warn( () -> "writing large cookie to response: cookieName=" + cookieName + ", length=" + value.length() );
         }
         }
         this.getHttpServletResponse().addCookie( theCookie );
         this.getHttpServletResponse().addCookie( theCookie );
-        addSameSiteCookieAttribute();
+        CookieManagementFilter.addSameSiteCookieAttribute( pwmRequest );
     }
     }
 
 
     public void removeCookie( final String cookieName, final PwmCookiePath path )
     public void removeCookie( final String cookieName, final PwmCookiePath path )

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

@@ -21,7 +21,7 @@
 package password.pwm.http;
 package password.pwm.http;
 
 
 import lombok.EqualsAndHashCode;
 import lombok.EqualsAndHashCode;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
@@ -243,7 +243,7 @@ public class PwmSession
         if ( pwmRequest != null )
         if ( pwmRequest != null )
         {
         {
 
 
-            final String nonceCookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_NAME );
+            final String nonceCookieName = pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_NONCE_NAME );
             pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, null );
             pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, null );
             pwmRequest.getPwmResponse().removeCookie( nonceCookieName, PwmCookiePath.Domain );
             pwmRequest.getPwmResponse().removeCookie( nonceCookieName, PwmCookiePath.Domain );
 
 
@@ -318,8 +318,8 @@ public class PwmSession
         securityKeyLock.lock();
         securityKeyLock.lock();
         try
         try
         {
         {
-            final int length = Integer.parseInt( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_LENGTH ) );
-            final String cookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_NAME );
+            final int length = Integer.parseInt( pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_NONCE_LENGTH ) );
+            final String cookieName = pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_NONCE_NAME );
 
 
             String nonce = ( String ) pwmRequest.getAttribute( PwmRequestAttribute.CookieNonce );
             String nonce = ( String ) pwmRequest.getAttribute( PwmRequestAttribute.CookieNonce );
             if ( nonce == null || nonce.length() < length )
             if ( nonce == null || nonce.length() < length )

+ 3 - 3
server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java

@@ -20,7 +20,7 @@
 
 
 package password.pwm.http.auth;
 package password.pwm.http.auth;
 
 
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -141,14 +141,14 @@ public abstract class HttpAuthenticationUtilities
 
 
         pwmSession.getLoginInfoBean().setFlag( LoginInfoBean.LoginFlag.authRecordSet );
         pwmSession.getLoginInfoBean().setFlag( LoginInfoBean.LoginFlag.authRecordSet );
 
 
-        final String cookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_NAME );
+        final String cookieName = pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_AUTHRECORD_NAME );
         if ( cookieName == null || cookieName.isEmpty() )
         if ( cookieName == null || cookieName.isEmpty() )
         {
         {
             LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie name parameter is blank" );
             LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie name parameter is blank" );
             return;
             return;
         }
         }
 
 
-        final int cookieAgeSeconds = Integer.parseInt( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_AGE ) );
+        final int cookieAgeSeconds = Integer.parseInt( pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_AUTHRECORD_AGE ) );
         if ( cookieAgeSeconds < 1 )
         if ( cookieAgeSeconds < 1 )
         {
         {
             LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie age parameter is less than 1" );
             LOGGER.debug( pwmRequest, () -> "skipping auth record cookie set, cookie age parameter is less than 1" );

+ 45 - 66
server/src/main/java/password/pwm/http/filter/CookieManagementFilter.java

@@ -20,123 +20,90 @@
 
 
 package password.pwm.http.filter;
 package password.pwm.http.filter;
 
 
-import password.pwm.AppProperty;
-import password.pwm.PwmApplication;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.ContextManager;
+import password.pwm.DomainProperty;
+import password.pwm.PwmApplicationMode;
+import password.pwm.config.DomainConfig;
+import password.pwm.error.PwmException;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
+import password.pwm.http.PwmURL;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.Collection;
+import java.util.Optional;
 
 
-public class CookieManagementFilter implements Filter
+public class CookieManagementFilter extends AbstractPwmFilter
 {
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( CookieManagementFilter.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( CookieManagementFilter.class );
 
 
-    private String value;
-
     @Override
     @Override
-    public void init( final FilterConfig filterConfig )
-            throws ServletException
+    void processFilter( final PwmApplicationMode mode, final PwmRequest pwmRequest, final PwmFilterChain filterChain )
+            throws PwmException, IOException, ServletException
     {
     {
-        final PwmApplication pwmApplication;
-        try
-        {
-            pwmApplication = ContextManager.getPwmApplication( filterConfig.getServletContext() );
-            value = pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_SAMESITE_VALUE );
-        }
-        catch ( final PwmUnrecoverableException e )
+        filterChain.doFilter();
+
+        if ( !pwmRequest.hasSession() )
         {
         {
-            LOGGER.trace( () -> "unable to load application configuration while checking samesite cookie attribute config" );
+            return;
         }
         }
-    }
 
 
-    @Override
-    public void destroy()
-    {
+        markSessionForRecycle( pwmRequest );
 
 
+        addSameSiteCookieAttribute( pwmRequest );
     }
     }
 
 
     @Override
     @Override
-    public void doFilter( final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain )
-            throws IOException, ServletException
+    boolean isInterested( final PwmApplicationMode mode, final PwmURL pwmURL )
     {
     {
-        filterChain.doFilter( servletRequest, servletResponse );
-        addSameSiteCookieAttribute( ( HttpServletResponse ) servletResponse, value );
-        markSessionForRecycle( ( HttpServletRequest ) servletRequest, ( HttpServletResponse ) servletResponse  );
+        return true;
     }
     }
 
 
     /**
     /**
      * Ensures that every session that modifies its samesite cookies also triggers a session ID
      * Ensures that every session that modifies its samesite cookies also triggers a session ID
      * recycle, once per session.
      * recycle, once per session.
      *
      *
-     * @param httpServletRequest The request to be marked
+     * @param pwmRequest The request to be marked
      */
      */
-    private void markSessionForRecycle( final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse )
+    private void markSessionForRecycle( final PwmRequest pwmRequest )
     {
     {
-        if ( StringUtil.isEmpty( value ) )
-        {
-            return;
-        }
-
-        final HttpSession httpSession = httpServletRequest.getSession( false );
-        if ( httpSession != null )
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        if ( pwmSession != null )
         {
         {
-            PwmSession pwmSession = null;
-            try
+            if ( !pwmSession.getSessionStateBean().isSameSiteCookieRecycleRequested() )
             {
             {
-                final PwmRequest pwmRequest = PwmRequest.forRequest( httpServletRequest, httpServletResponse );
-                pwmSession = pwmRequest.getPwmSession();
-            }
-            catch ( final PwmUnrecoverableException e )
-            {
-                LOGGER.trace( () -> "unable to load session while checking samesite cookie attribute config" );
-            }
-
-            if ( pwmSession != null )
-            {
-                if ( !pwmSession.getSessionStateBean().isSameSiteCookieRecycleRequested() )
-                {
-                    pwmSession.getSessionStateBean().setSameSiteCookieRecycleRequested( true );
-                    pwmSession.getSessionStateBean().setSessionIdRecycleNeeded( true );
-                }
+                pwmSession.getSessionStateBean().setSameSiteCookieRecycleRequested( true );
+                pwmSession.getSessionStateBean().setSessionIdRecycleNeeded( true );
             }
             }
         }
         }
     }
     }
 
 
-    public static void addSameSiteCookieAttribute( final HttpServletResponse response, final String value )
+    public static void addSameSiteCookieAttribute( final PwmRequest pwmRequest )
     {
     {
-        if ( StringUtil.isEmpty( value ) )
+        final Optional<String> sameSiteValue = sameSiteCookieValue( pwmRequest );
+        if ( sameSiteValue.isEmpty() )
         {
         {
             return;
             return;
         }
         }
 
 
-        final Collection<String> headers = response.getHeaders( HttpHeader.SetCookie.getHttpName() );
+        final HttpServletResponse response = pwmRequest.getPwmResponse().getHttpServletResponse();
+        final Collection<String> rawCookieValues = response.getHeaders( HttpHeader.SetCookie.getHttpName() );
         boolean firstHeader = true;
         boolean firstHeader = true;
 
 
-        for ( final String header : headers )
+        for ( final String rawCookieValue : rawCookieValues )
         {
         {
             final String newHeader;
             final String newHeader;
-            if ( !header.contains( "SameSite" ) )
+            if ( !rawCookieValue.contains( "SameSite" ) )
             {
             {
-                newHeader = header + "; SameSite=" + value;
+                newHeader = rawCookieValue + "; SameSite=" + sameSiteValue.get();
             }
             }
             else
             else
             {
             {
-                newHeader = header;
+                newHeader = rawCookieValue;
             }
             }
 
 
             if ( firstHeader )
             if ( firstHeader )
@@ -150,4 +117,16 @@ public class CookieManagementFilter implements Filter
             }
             }
         }
         }
     }
     }
+
+    private static Optional<String> sameSiteCookieValue( final PwmRequest pwmRequest )
+    {
+        final DomainConfig domainConfig = pwmRequest.getDomainConfig();
+        final String value = domainConfig.readDomainProperty( DomainProperty.HTTP_COOKIE_SAMESITE_VALUE );
+        if ( StringUtil.isTrimEmpty( value ) )
+        {
+            return Optional.of( value );
+        }
+
+        return Optional.empty();
+    }
 }
 }

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

@@ -21,6 +21,7 @@
 package password.pwm.http.filter;
 package password.pwm.http.filter;
 
 
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
@@ -559,7 +560,7 @@ public class RequestInitializationFilter implements Filter
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final String themeCookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_THEME_NAME );
+        final String themeCookieName = pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_THEME_NAME );
         if ( StringUtil.notEmpty( themeCookieName ) )
         if ( StringUtil.notEmpty( themeCookieName ) )
         {
         {
             final Optional<String> themeCookie = pwmRequest.readCookie( themeCookieName );
             final Optional<String> themeCookie = pwmRequest.readCookie( themeCookieName );

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

@@ -21,6 +21,7 @@
 package password.pwm.http.filter;
 package password.pwm.http.filter;
 
 
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
@@ -396,7 +397,7 @@ public class SessionFilter extends AbstractPwmFilter
                 if ( pwmRequest.getPwmDomain().getResourceServletService().checkIfThemeExists( pwmRequest, themeReqParameter ) )
                 if ( pwmRequest.getPwmDomain().getResourceServletService().checkIfThemeExists( pwmRequest, themeReqParameter ) )
                 {
                 {
                     pwmRequest.getPwmSession().getSessionStateBean().setTheme( themeReqParameter );
                     pwmRequest.getPwmSession().getSessionStateBean().setTheme( themeReqParameter );
-                    final String themeCookieName = config.readAppProperty( AppProperty.HTTP_COOKIE_THEME_NAME );
+                    final String themeCookieName = config.readDomainProperty( DomainProperty.HTTP_COOKIE_THEME_NAME );
                     if ( themeCookieName != null && themeCookieName.length() > 0 )
                     if ( themeCookieName != null && themeCookieName.length() > 0 )
                     {
                     {
                         final String configuredTheme = config.readSettingAsString( PwmSetting.INTERFACE_THEME );
                         final String configuredTheme = config.readSettingAsString( PwmSetting.INTERFACE_THEME );
@@ -407,7 +408,7 @@ public class SessionFilter extends AbstractPwmFilter
                         }
                         }
                         else
                         else
                         {
                         {
-                            final int maxAge = Integer.parseInt( config.readAppProperty( AppProperty.HTTP_COOKIE_THEME_AGE ) );
+                            final int maxAge = Integer.parseInt( config.readDomainProperty( DomainProperty.HTTP_COOKIE_THEME_AGE ) );
                             pwmRequest.getPwmResponse().writeCookie( themeCookieName, themeReqParameter, maxAge, PwmCookiePath.Domain );
                             pwmRequest.getPwmResponse().writeCookie( themeCookieName, themeReqParameter, maxAge, PwmCookiePath.Domain );
                         }
                         }
                     }
                     }

+ 3 - 2
server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import lombok.Data;
 import lombok.Data;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.Permission;
 import password.pwm.Permission;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
@@ -307,8 +308,8 @@ public class ClientApiServlet extends ControlledPwmServlet
         final DomainConfig config = pwmDomain.getConfig();
         final DomainConfig config = pwmDomain.getConfig();
         final TreeMap<String, Object> settingMap = new TreeMap<>();
         final TreeMap<String, Object> settingMap = new TreeMap<>();
 
 
-        settingMap.put( "client.ajaxTypingTimeout", Integer.parseInt( config.readAppProperty( AppProperty.CLIENT_AJAX_TYPING_TIMEOUT ) ) );
-        settingMap.put( "client.ajaxTypingWait", Integer.parseInt( config.readAppProperty( AppProperty.CLIENT_AJAX_TYPING_WAIT ) ) );
+        settingMap.put( "client.ajaxTypingTimeout", Integer.parseInt( config.readDomainProperty( DomainProperty.CLIENT_AJAX_TYPING_TIMEOUT ) ) );
+        settingMap.put( "client.ajaxTypingWait", Integer.parseInt( config.readDomainProperty( DomainProperty.CLIENT_AJAX_TYPING_WAIT ) ) );
         settingMap.put( "client.activityMaxEpsRate", Integer.parseInt( config.readAppProperty( AppProperty.CLIENT_ACTIVITY_MAX_EPS_RATE ) ) );
         settingMap.put( "client.activityMaxEpsRate", Integer.parseInt( config.readAppProperty( AppProperty.CLIENT_ACTIVITY_MAX_EPS_RATE ) ) );
         settingMap.put( "client.locale", LocaleHelper.getBrowserLocaleString( pwmSession.getSessionStateBean().getLocale() ) );
         settingMap.put( "client.locale", LocaleHelper.getBrowserLocaleString( pwmSession.getSessionStateBean().getLocale() ) );
         settingMap.put( "client.pwShowRevertTimeout", Integer.parseInt( config.readAppProperty( AppProperty.CLIENT_PW_SHOW_REVERT_TIMEOUT ) ) );
         settingMap.put( "client.pwShowRevertTimeout", Integer.parseInt( config.readAppProperty( AppProperty.CLIENT_PW_SHOW_REVERT_TIMEOUT ) ) );

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -31,7 +31,7 @@ import com.novell.ldapchai.exception.ChaiException;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiValidationException;
 import com.novell.ldapchai.exception.ChaiValidationException;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.EmailItemBean;
@@ -228,7 +228,7 @@ public class ForgottenPasswordUtil
 
 
         try
         try
         {
         {
-            final String cookieName = pwmDomain.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_NAME );
+            final String cookieName = pwmDomain.getConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_AUTHRECORD_NAME );
             if ( cookieName == null || cookieName.isEmpty() )
             if ( cookieName == null || cookieName.isEmpty() )
             {
             {
                 LOGGER.trace( pwmRequest, () -> "skipping auth record cookie read, cookie name parameter is blank" );
                 LOGGER.trace( pwmRequest, () -> "skipping auth record cookie read, cookie name parameter is blank" );

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

@@ -20,7 +20,7 @@
 
 
 package password.pwm.http.state;
 package password.pwm.http.state;
 
 
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
@@ -37,8 +37,9 @@ import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.stats.StatisticsClient;
-import password.pwm.util.json.JsonFactory;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
+import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
@@ -50,18 +51,24 @@ class CryptoCookieLoginImpl implements SessionLoginProvider
     private static final PwmLogger LOGGER = PwmLogger.forClass( CryptoCookieLoginImpl.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( CryptoCookieLoginImpl.class );
 
 
     private static final PwmCookiePath COOKIE_PATH = PwmCookiePath.Domain;
     private static final PwmCookiePath COOKIE_PATH = PwmCookiePath.Domain;
-    private String cookieName = "SESSION";
 
 
     @Override
     @Override
     public void init( final PwmApplication pwmApplication ) throws PwmException
     public void init( final PwmApplication pwmApplication ) throws PwmException
     {
     {
-        cookieName = pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_LOGIN_NAME );
+    }
+
+    private String cookieName( final PwmRequest pwmRequest )
+    {
+        final String cookieName = pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_LOGIN_NAME );
+        return StringUtil.isEmpty( cookieName )
+                ? "SESSION"
+                : cookieName;
     }
     }
 
 
     @Override
     @Override
     public void clearLoginSession( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
     public void clearLoginSession( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
     {
     {
-        pwmRequest.getPwmResponse().removeCookie( cookieName, COOKIE_PATH );
+        pwmRequest.getPwmResponse().removeCookie( cookieName( pwmRequest ), COOKIE_PATH );
     }
     }
 
 
     @Override
     @Override
@@ -73,7 +80,7 @@ class CryptoCookieLoginImpl implements SessionLoginProvider
             loginInfoBean.setReqTime( Instant.now() );
             loginInfoBean.setReqTime( Instant.now() );
 
 
             pwmRequest.getPwmResponse().writeEncryptedCookie(
             pwmRequest.getPwmResponse().writeEncryptedCookie(
-                    cookieName,
+                    cookieName( pwmRequest ),
                     loginInfoBean,
                     loginInfoBean,
                     COOKIE_PATH
                     COOKIE_PATH
             );
             );
@@ -98,7 +105,7 @@ class CryptoCookieLoginImpl implements SessionLoginProvider
         final Optional<LoginInfoBean> optionalRemoteLoginCookie;
         final Optional<LoginInfoBean> optionalRemoteLoginCookie;
         try
         try
         {
         {
-            optionalRemoteLoginCookie = pwmRequest.readEncryptedCookie( cookieName, LoginInfoBean.class );
+            optionalRemoteLoginCookie = pwmRequest.readEncryptedCookie( cookieName( pwmRequest ), LoginInfoBean.class );
         }
         }
         catch ( final PwmUnrecoverableException e )
         catch ( final PwmUnrecoverableException e )
         {
         {

+ 15 - 9
server/src/main/java/password/pwm/ldap/LdapBrowser.java

@@ -32,7 +32,7 @@ import com.novell.ldapchai.util.ChaiUtility;
 import com.novell.ldapchai.util.SearchHelper;
 import com.novell.ldapchai.util.SearchHelper;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Value;
 import lombok.Value;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.ProfileID;
 import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
@@ -188,10 +188,11 @@ public class LdapBrowser
         }
         }
         result.navigableDNlist( navigableDNs );
         result.navigableDNlist( navigableDNs );
         result.selectableDNlist( selectableDNs );
         result.selectableDNlist( selectableDNs );
-        result.maxResults( childDNs.size() >= getMaxSizeLimit() );
+        result.maxResults( childDNs.size() >= getMaxSizeLimit( domainID, storedConfiguration ) );
     }
     }
 
 
-    private ChaiProvider getChaiProvider( final DomainID domainID, final ProfileID profile ) throws PwmUnrecoverableException
+    private ChaiProvider getChaiProvider( final DomainID domainID, final ProfileID profile )
+            throws PwmUnrecoverableException
     {
     {
         if ( !providerCache.containsKey( profile ) )
         if ( !providerCache.containsKey( profile ) )
         {
         {
@@ -203,10 +204,13 @@ public class LdapBrowser
         return providerCache.get( profile );
         return providerCache.get( profile );
     }
     }
 
 
-    private int getMaxSizeLimit( )
+    private static int getMaxSizeLimit(
+            final DomainID domainID,
+            final StoredConfiguration storedConfiguration
+    )
     {
     {
-        final AppConfig appConfig = AppConfig.forStoredConfig( storedConfiguration );
-        return Integer.parseInt( appConfig.readAppProperty( AppProperty.LDAP_BROWSER_MAX_ENTRIES ) );
+        final DomainConfig domainConfig = AppConfig.forStoredConfig( storedConfiguration ).getDomainConfigs().get( domainID );
+        return Integer.parseInt( domainConfig.readDomainProperty( DomainProperty.LDAP_BROWSER_MAX_ENTRIES ) );
     }
     }
 
 
     private Map<String, DnType> getChildEntries(
     private Map<String, DnType> getChildEntries(
@@ -227,7 +231,7 @@ public class LdapBrowser
             ) ) );
             ) ) );
         }
         }
 
 
-        final Set<String> results = doLdapSearch( dn, chaiProvider );
+        final Set<String> results = doLdapSearch( domainID, dn, chaiProvider );
 
 
         final HashMap<String, DnType> returnMap = new LinkedHashMap<>( results.size() );
         final HashMap<String, DnType> returnMap = new LinkedHashMap<>( results.size() );
         for ( final String resultDN : results )
         for ( final String resultDN : results )
@@ -252,14 +256,16 @@ public class LdapBrowser
     }
     }
 
 
     private Set<String> doLdapSearch(
     private Set<String> doLdapSearch(
-            final String dn, final ChaiProvider chaiProvider
+            final DomainID domainID,
+            final String dn,
+            final ChaiProvider chaiProvider
     )
     )
             throws ChaiUnavailableException, ChaiOperationException
             throws ChaiUnavailableException, ChaiOperationException
     {
     {
         final SearchHelper searchHelper = new SearchHelper();
         final SearchHelper searchHelper = new SearchHelper();
         searchHelper.setFilter( SearchHelper.DEFAULT_FILTER );
         searchHelper.setFilter( SearchHelper.DEFAULT_FILTER );
         searchHelper.setAttributes( Collections.emptyList() );
         searchHelper.setAttributes( Collections.emptyList() );
-        searchHelper.setMaxResults( getMaxSizeLimit() );
+        searchHelper.setMaxResults( getMaxSizeLimit( domainID, storedConfiguration ) );
         searchHelper.setSearchScope( SearchScope.ONE );
         searchHelper.setSearchScope( SearchScope.ONE );
 
 
         return chaiProvider.search( dn, searchHelper ).keySet();
         return chaiProvider.search( dn, searchHelper ).keySet();

+ 3 - 3
server/src/main/java/password/pwm/ldap/LdapDomainService.java

@@ -26,7 +26,7 @@ import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.provider.ProviderStatistics;
 import com.novell.ldapchai.provider.ProviderStatistics;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Value;
 import lombok.Value;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.DomainID;
@@ -281,8 +281,8 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
 
 
     private int maxSlotsPerProfile( final PwmDomain pwmDomain )
     private int maxSlotsPerProfile( final PwmDomain pwmDomain )
     {
     {
-        final int maxConnections = Integer.parseInt( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PROXY_MAX_CONNECTIONS ) );
-        final int perProfile = Integer.parseInt( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PROXY_CONNECTION_PER_PROFILE ) );
+        final int maxConnections = Integer.parseInt( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_PROXY_MAX_CONNECTIONS ) );
+        final int perProfile = Integer.parseInt( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_PROXY_CONNECTION_PER_PROFILE ) );
         final int profileCount = pwmDomain.getConfig().getLdapProfiles().size();
         final int profileCount = pwmDomain.getConfig().getLdapProfiles().size();
 
 
         if ( ( perProfile * profileCount ) >= maxConnections )
         if ( ( perProfile * profileCount ) >= maxConnections )

+ 2 - 2
server/src/main/java/password/pwm/ldap/LdapGuidReaderUtil.java

@@ -23,7 +23,7 @@ package password.pwm.ldap;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
@@ -240,7 +240,7 @@ class LdapGuidReaderUtil
     {
     {
         final MacroRequest macroRequest = MacroRequest.forNonUserSpecific( pwmDomain.getPwmApplication(),
         final MacroRequest macroRequest = MacroRequest.forNonUserSpecific( pwmDomain.getPwmApplication(),
                 sessionLabel );
                 sessionLabel );
-        final String guidPattern = pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_GUID_PATTERN );
+        final String guidPattern = pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_GUID_PATTERN );
         return macroRequest.expandMacros( guidPattern );
         return macroRequest.expandMacros( guidPattern );
     }
     }
 }
 }

+ 9 - 8
server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java

@@ -35,6 +35,7 @@ import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.provider.SearchScope;
 import com.novell.ldapchai.provider.SearchScope;
 import com.novell.ldapchai.util.SearchHelper;
 import com.novell.ldapchai.util.SearchHelper;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.PhotoDataBean;
 import password.pwm.bean.PhotoDataBean;
@@ -208,7 +209,7 @@ public class LdapOperationsHelper
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final boolean enableCache = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_CACHE_USER_GUID_ENABLE ) );
+        final boolean enableCache = Boolean.parseBoolean( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_CACHE_USER_GUID_ENABLE ) );
         final CacheKey cacheKey = CacheKey.newKey( LdapOperationsHelper.class, userIdentity, "guidValue" );
         final CacheKey cacheKey = CacheKey.newKey( LdapOperationsHelper.class, userIdentity, "guidValue" );
 
 
         if ( enableCache )
         if ( enableCache )
@@ -250,7 +251,7 @@ public class LdapOperationsHelper
 
 
         if ( enableCache )
         if ( enableCache )
         {
         {
-            final long cacheSeconds = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_CACHE_USER_GUID_SECONDS ) );
+            final long cacheSeconds = Long.parseLong( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_CACHE_USER_GUID_SECONDS ) );
             final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration( TimeDuration.of( cacheSeconds, TimeDuration.Unit.SECONDS ) );
             final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration( TimeDuration.of( cacheSeconds, TimeDuration.Unit.SECONDS ) );
             final String cacheValue = existingValue.orElse( NULL_CACHE_GUID );
             final String cacheValue = existingValue.orElse( NULL_CACHE_GUID );
             pwmDomain.getCacheService().put( cacheKey, cachePolicy, cacheValue );
             pwmDomain.getCacheService().put( cacheKey, cachePolicy, cacheValue );
@@ -536,9 +537,9 @@ public class LdapOperationsHelper
                         : userPassword.getStringValue()
                         : userPassword.getStringValue()
         );
         );
 
 
-        configBuilder.setSetting( ChaiSetting.PROMISCUOUS_SSL, config.readAppProperty( AppProperty.LDAP_PROMISCUOUS_ENABLE ) );
+        configBuilder.setSetting( ChaiSetting.PROMISCUOUS_SSL, config.readDomainProperty( DomainProperty.LDAP_PROMISCUOUS_ENABLE ) );
         {
         {
-            final boolean enableNmasExtensions = Boolean.parseBoolean( config.readAppProperty( AppProperty.LDAP_EXTENSIONS_NMAS_ENABLE ) );
+            final boolean enableNmasExtensions = Boolean.parseBoolean( config.readDomainProperty( DomainProperty.LDAP_EXTENSIONS_NMAS_ENABLE ) );
             configBuilder.setSetting( ChaiSetting.EDIRECTORY_ENABLE_NMAS, Boolean.toString( enableNmasExtensions ) );
             configBuilder.setSetting( ChaiSetting.EDIRECTORY_ENABLE_NMAS, Boolean.toString( enableNmasExtensions ) );
         }
         }
 
 
@@ -590,7 +591,7 @@ public class LdapOperationsHelper
             );
             );
         }
         }
 
 
-        final String idleTimeoutMsString = config.readAppProperty( AppProperty.LDAP_CONNECTION_TIMEOUT );
+        final String idleTimeoutMsString = config.readDomainProperty( DomainProperty.LDAP_CONNECTION_TIMEOUT );
         configBuilder.setSetting( ChaiSetting.LDAP_CONNECT_TIMEOUT, idleTimeoutMsString );
         configBuilder.setSetting( ChaiSetting.LDAP_CONNECT_TIMEOUT, idleTimeoutMsString );
 
 
         // set the watchdog idle timeout.
         // set the watchdog idle timeout.
@@ -605,8 +606,8 @@ public class LdapOperationsHelper
             configBuilder.setSetting( ChaiSetting.WATCHDOG_ENABLE, "false" );
             configBuilder.setSetting( ChaiSetting.WATCHDOG_ENABLE, "false" );
         }
         }
 
 
-        configBuilder.setSetting( ChaiSetting.LDAP_SEARCH_PAGING_ENABLE, config.readAppProperty( AppProperty.LDAP_SEARCH_PAGING_ENABLE ) );
-        configBuilder.setSetting( ChaiSetting.LDAP_SEARCH_PAGING_SIZE, config.readAppProperty( AppProperty.LDAP_SEARCH_PAGING_SIZE ) );
+        configBuilder.setSetting( ChaiSetting.LDAP_SEARCH_PAGING_ENABLE, config.readDomainProperty( DomainProperty.LDAP_SEARCH_PAGING_ENABLE ) );
+        configBuilder.setSetting( ChaiSetting.LDAP_SEARCH_PAGING_SIZE, config.readDomainProperty( DomainProperty.LDAP_SEARCH_PAGING_SIZE ) );
 
 
         if ( config.readSettingAsBoolean( PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET ) )
         if ( config.readSettingAsBoolean( PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET ) )
         {
         {
@@ -614,7 +615,7 @@ public class LdapOperationsHelper
         }
         }
 
 
         // write out any configured values;
         // write out any configured values;
-        final String rawValue = config.readAppProperty( AppProperty.LDAP_CHAI_SETTINGS );
+        final String rawValue = config.readDomainProperty( DomainProperty.LDAP_CHAI_SETTINGS );
         final String[] rawValues = rawValue != null ? rawValue.split( AppProperty.VALUE_SEPARATOR ) : new String[ 0 ];
         final String[] rawValues = rawValue != null ? rawValue.split( AppProperty.VALUE_SEPARATOR ) : new String[ 0 ];
         final Map<String, String> configuredSettings = StringUtil.convertStringListToNameValuePair( Arrays.asList( rawValues ), "=" );
         final Map<String, String> configuredSettings = StringUtil.convertStringListToNameValuePair( Arrays.asList( rawValues ), "=" );
         for ( final Map.Entry<String, String> entry : configuredSettings.entrySet() )
         for ( final Map.Entry<String, String> entry : configuredSettings.entrySet() )

+ 3 - 3
server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java

@@ -23,7 +23,7 @@ package password.pwm.ldap;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Data;
 import lombok.Data;
 import lombok.Value;
 import lombok.Value;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
@@ -275,8 +275,8 @@ public class PasswordChangeProgressChecker
 
 
     private Optional<ProgressRecord> figureReplicationStatusCompletion( final ProgressTracker tracker )
     private Optional<ProgressRecord> figureReplicationStatusCompletion( final ProgressTracker tracker )
     {
     {
-        final long initDelayMs = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS ) );
-        final long cycleDelayMs = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS ) );
+        final long initDelayMs = Long.parseLong( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS ) );
+        final long cycleDelayMs = Long.parseLong( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS ) );
         final TimeDuration initialReplicaDelay = TimeDuration.of( initDelayMs, TimeDuration.Unit.MILLISECONDS );
         final TimeDuration initialReplicaDelay = TimeDuration.of( initDelayMs, TimeDuration.Unit.MILLISECONDS );
         final TimeDuration cycleReplicaDelay = TimeDuration.of( cycleDelayMs, TimeDuration.Unit.MILLISECONDS );
         final TimeDuration cycleReplicaDelay = TimeDuration.of( cycleDelayMs, TimeDuration.Unit.MILLISECONDS );
 
 

+ 2 - 1
server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java

@@ -32,6 +32,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.provider.DirectoryVendor;
 import com.novell.ldapchai.provider.DirectoryVendor;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
@@ -594,7 +595,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                     ORACLE_ATTR_PW_ALLOW_CHG_TIME );
                     ORACLE_ATTR_PW_ALLOW_CHG_TIME );
             if ( oracleDSPostPasswordAllowChangeTime != null && !oracleDSPostPasswordAllowChangeTime.isEmpty() )
             if ( oracleDSPostPasswordAllowChangeTime != null && !oracleDSPostPasswordAllowChangeTime.isEmpty() )
             {
             {
-                final boolean postTempUseCurrentTime = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_ORACLE_POST_TEMPPW_USE_CURRENT_TIME ) );
+                final boolean postTempUseCurrentTime = Boolean.parseBoolean( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_ORACLE_POST_TEMPPW_USE_CURRENT_TIME ) );
                 if ( postTempUseCurrentTime )
                 if ( postTempUseCurrentTime )
                 {
                 {
                     log( PwmLogLevel.TRACE, () -> "a new value for passwordAllowChangeTime attribute to user "
                     log( PwmLogLevel.TRACE, () -> "a new value for passwordAllowChangeTime attribute to user "

+ 5 - 5
server/src/main/java/password/pwm/ldap/search/UserSearchService.java

@@ -21,7 +21,7 @@
 package password.pwm.ldap.search;
 package password.pwm.ldap.search;
 
 
 import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProvider;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
@@ -299,7 +299,7 @@ public class UserSearchService extends AbstractPwmService implements PwmService
 
 
         counters.increment( SearchStatistic.searchCounter );
         counters.increment( SearchStatistic.searchCounter );
         final int searchID = searchIdCounter.next();
         final int searchID = searchIdCounter.next();
-        final long profileRetryDelayMS = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PROFILE_RETRY_DELAY ) );
+        final long profileRetryDelayMS = Long.parseLong( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_PROFILE_RETRY_DELAY ) );
         final AtomicLoopIntIncrementer jobIncrementer = AtomicLoopIntIncrementer.builder().build();
         final AtomicLoopIntIncrementer jobIncrementer = AtomicLoopIntIncrementer.builder().build();
 
 
         final List<UserSearchJob> searchJobs = new ArrayList<>();
         final List<UserSearchJob> searchJobs = new ArrayList<>();
@@ -744,7 +744,7 @@ public class UserSearchService extends AbstractPwmService implements PwmService
     {
     {
         final DomainConfig domainConfig = pwmDomain.getConfig();
         final DomainConfig domainConfig = pwmDomain.getConfig();
 
 
-        final boolean enabled = Boolean.parseBoolean( domainConfig.readAppProperty( AppProperty.LDAP_SEARCH_PARALLEL_ENABLE ) );
+        final boolean enabled = Boolean.parseBoolean( domainConfig.readDomainProperty( DomainProperty.LDAP_SEARCH_PARALLEL_ENABLE ) );
         if ( !enabled )
         if ( !enabled )
         {
         {
             return null;
             return null;
@@ -763,8 +763,8 @@ public class UserSearchService extends AbstractPwmService implements PwmService
 
 
         if ( endPoints > 1 )
         if ( endPoints > 1 )
         {
         {
-            final int factor = Integer.parseInt( domainConfig.readAppProperty( AppProperty.LDAP_SEARCH_PARALLEL_FACTOR ) );
-            final int maxThreads = Integer.parseInt( domainConfig.readAppProperty( AppProperty.LDAP_SEARCH_PARALLEL_THREAD_MAX ) );
+            final int factor = Integer.parseInt( domainConfig.readDomainProperty( DomainProperty.LDAP_SEARCH_PARALLEL_FACTOR ) );
+            final int maxThreads = Integer.parseInt( domainConfig.readDomainProperty( DomainProperty.LDAP_SEARCH_PARALLEL_THREAD_MAX ) );
             final int threads = Math.min( maxThreads, ( endPoints ) * factor );
             final int threads = Math.min( maxThreads, ( endPoints ) * factor );
             final int minThreads = JavaHelper.rangeCheck( 1, 10, endPoints );
             final int minThreads = JavaHelper.rangeCheck( 1, 10, endPoints );
 
 

+ 4 - 3
server/src/main/java/password/pwm/util/CaptchaUtility.java

@@ -24,6 +24,7 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.JsonParser;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.DomainConfig;
@@ -233,8 +234,8 @@ public class CaptchaUtility
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final String cookieValue = figureSkipCookieValue( pwmRequest );
         final String cookieValue = figureSkipCookieValue( pwmRequest );
-        final int captchaSkipCookieLifetimeSeconds = Integer.parseInt( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_CAPTCHA_SKIP_AGE ) );
-        final String captchaSkipCookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_CAPTCHA_SKIP_NAME );
+        final int captchaSkipCookieLifetimeSeconds = Integer.parseInt( pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_CAPTCHA_SKIP_AGE ) );
+        final String captchaSkipCookieName = pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_CAPTCHA_SKIP_NAME );
         if ( cookieValue != null )
         if ( cookieValue != null )
         {
         {
             pwmRequest.getPwmResponse().writeCookie(
             pwmRequest.getPwmResponse().writeCookie(
@@ -269,7 +270,7 @@ public class CaptchaUtility
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final String allowedSkipValue = figureSkipCookieValue( pwmRequest );
         final String allowedSkipValue = figureSkipCookieValue( pwmRequest );
-        final String captchaSkipCookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_CAPTCHA_SKIP_NAME );
+        final String captchaSkipCookieName = pwmRequest.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_CAPTCHA_SKIP_NAME );
         if ( allowedSkipValue != null )
         if ( allowedSkipValue != null )
         {
         {
             final Optional<String> cookieValue = pwmRequest.readCookie( captchaSkipCookieName );
             final Optional<String> cookieValue = pwmRequest.readCookie( captchaSkipCookieName );

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

@@ -25,6 +25,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.bean.FormNonce;
 import password.pwm.bean.FormNonce;
 import password.pwm.config.AppConfig;
 import password.pwm.config.AppConfig;
+import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -152,7 +153,7 @@ public class Validator
     }
     }
 
 
 
 
-    public static String sanitizeHeaderValue( final AppConfig domainConfig, final String input )
+    public static String sanitizeHeaderValue( final DomainConfig domainConfig, final String input )
     {
     {
         if ( input == null )
         if ( input == null )
         {
         {

+ 3 - 2
server/src/main/java/password/pwm/util/password/PasswordUtility.java

@@ -36,6 +36,7 @@ import com.novell.ldapchai.util.ChaiUtility;
 import com.nulabinc.zxcvbn.Strength;
 import com.nulabinc.zxcvbn.Strength;
 import com.nulabinc.zxcvbn.Zxcvbn;
 import com.nulabinc.zxcvbn.Zxcvbn;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LocalSessionStateBean;
@@ -475,7 +476,7 @@ public class PasswordUtility
 
 
             LOGGER.trace( sessionLabel, () -> "preparing to setActorPassword for '" + theUser.getEntryDN() + "', using bind DN: " + bindDN );
             LOGGER.trace( sessionLabel, () -> "preparing to setActorPassword for '" + theUser.getEntryDN() + "', using bind DN: " + bindDN );
 
 
-            final boolean settingEnableChange = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PASSWORD_CHANGE_SELF_ENABLE ) );
+            final boolean settingEnableChange = Boolean.parseBoolean( pwmDomain.getConfig().readDomainProperty( DomainProperty.LDAP_PASSWORD_CHANGE_SELF_ENABLE ) );
             if ( settingEnableChange )
             if ( settingEnableChange )
             {
             {
                 if ( oldPassword == null )
                 if ( oldPassword == null )
@@ -490,7 +491,7 @@ public class PasswordUtility
             else
             else
             {
             {
                 LOGGER.debug( sessionLabel, () -> "skipping actual ldap password change operation due to app property "
                 LOGGER.debug( sessionLabel, () -> "skipping actual ldap password change operation due to app property "
-                        + AppProperty.LDAP_PASSWORD_CHANGE_SELF_ENABLE.getKey() + "=false" );
+                        + DomainProperty.LDAP_PASSWORD_CHANGE_SELF_ENABLE.getKey() + "=false" );
             }
             }
         }
         }
         catch ( final ChaiPasswordPolicyException e )
         catch ( final ChaiPasswordPolicyException e )

+ 2 - 2
server/src/main/java/password/pwm/util/secure/BeanCryptoMachine.java

@@ -21,7 +21,7 @@
 package password.pwm.util.secure;
 package password.pwm.util.secure;
 
 
 import lombok.Value;
 import lombok.Value;
-import password.pwm.AppProperty;
+import password.pwm.DomainProperty;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
@@ -53,7 +53,7 @@ public class BeanCryptoMachine<T extends Object>
 
 
     private String newKey()
     private String newKey()
     {
     {
-        final int length = Integer.parseInt( pwmRequestContext.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_LENGTH ) );
+        final int length = Integer.parseInt( pwmRequestContext.getDomainConfig().readDomainProperty( DomainProperty.HTTP_COOKIE_NONCE_LENGTH ) );
 
 
         final String random = pwmRequestContext.getPwmDomain().getSecureService().pwmRandom().alphaNumericString( length );
         final String random = pwmRequestContext.getPwmDomain().getSecureService().pwmRandom().alphaNumericString( length );
 
 

+ 1 - 42
server/src/main/resources/password/pwm/AppProperty.properties

@@ -48,8 +48,6 @@ cache.pwRuleCheckLifetimeMS=30000
 cache.uniqueFormValueLifetimeMS=30000
 cache.uniqueFormValueLifetimeMS=30000
 client.ajax.activityMaxEpsRate=100
 client.ajax.activityMaxEpsRate=100
 client.ajax.changePasswordWaitCheckSeconds=3
 client.ajax.changePasswordWaitCheckSeconds=3
-client.ajax.typingTimeout=20000
-client.ajax.typingWait=700
 client.formNonce.enable=true
 client.formNonce.enable=true
 client.formNonce.length=10
 client.formNonce.length=10
 client.form.clientRegexEnable=true
 client.form.clientRegexEnable=true
@@ -146,20 +144,6 @@ http.header.sendXXSSProtection=true
 http.header.noise.length=128
 http.header.noise.length=128
 http.header.csp.nonce.bytes=24
 http.header.csp.nonce.bytes=24
 http.header.cacheControl=no-cache, no-store, must-revalidate, proxy-revalidate
 http.header.cacheControl=no-cache, no-store, must-revalidate, proxy-revalidate
-http.cookie.default.secureFlag=auto
-http.cookie.httponly.enable=true
-http.cookie.theme.name=theme
-http.cookie.theme.age=604800
-http.cookie.locale.name=locale
-http.cookie.authRecord.name=authRecord
-http.cookie.authRecord.age=604800
-http.cookie.maxReadLength=10240
-http.cookie.captchaSkip.name=captcha-key
-http.cookie.captchaSkip.age=86400
-http.cookie.login.name=SESSION
-http.cookie.nonce.name=ID
-http.cookie.nonce.length=32
-http.cookie.sameSite.value=Strict
 http.parameter.forward=forwardURL
 http.parameter.forward=forwardURL
 http.parameter.logout=logoutURL
 http.parameter.logout=logoutURL
 http.parameter.theme=theme
 http.parameter.theme=theme
@@ -191,31 +175,6 @@ intruder.delayPerCountMS=200
 intruder.delayMaxJitterMS=2000
 intruder.delayMaxJitterMS=2000
 intruder.storageHashAlgorithm=SHA256
 intruder.storageHashAlgorithm=SHA256
 l10n.rtl.regex=^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)
 l10n.rtl.regex=^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)
-ldap.resolveCanonicalDN=true
-ldap.cache.canonical.enable=true
-ldap.cache.canonical.seconds=600
-ldap.cache.userGuid.enable=true
-ldap.cache.userGuid.seconds=3600
-ldap.chaiSettings=
-ldap.proxy.connectionsPerProfile=10
-ldap.proxy.maxConnections=50
-ldap.proxy.idleThreadLocal.timeoutMS=90000
-ldap.extensions.nmas.enable=true
-ldap.connection.timeoutMS=30000
-ldap.profile.retryDelayMS=30000
-ldap.promiscuousEnable=false
-ldap.password.replicaCheck.initialDelayMS=1000
-ldap.password.replicaCheck.cycleDelayMS=7000
-ldap.password.change.self.enable=true
-ldap.password.change.helpdesk.enable=true
-ldap.guid.pattern=@UUID@
-ldap.browser.maxEntries=1000
-ldap.search.paging.enable=auto
-ldap.search.paging.size=500
-ldap.search.parallel.enable=true
-ldap.search.parallel.factor=5
-ldap.search.parallel.threadMax=50
-ldap.oracle.postTempPasswordUseCurrentTime=false
 localdb.aggressiveCompact.enabled=false
 localdb.aggressiveCompact.enabled=false
 localdb.implementation=password.pwm.util.localdb.XodusLocalDB
 localdb.implementation=password.pwm.util.localdb.XodusLocalDB
 localdb.initParameters=
 localdb.initParameters=
@@ -313,7 +272,7 @@ security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
 security.http.performCsrfHeaderChecks=false
 security.http.performCsrfHeaderChecks=false
 security.http.promiscuousEnable=false
 security.http.promiscuousEnable=false
 security.http.permittedUserPhotoMimeTypes=image/gif,image/png,image/jpeg
 security.http.permittedUserPhotoMimeTypes=image/gif,image/png,image/jpeg
-security.http.permittedUrlPathCharacters=^[a-zA-Z0-9-]*$
+security.http.permittedUrlPathCharacters=^[a-zA-Z0-9-_]*$
 security.http.config.cspHeader=default-src 'self'; object-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; report-uri @PwmContextPath@/public/command/cspReport
 security.http.config.cspHeader=default-src 'self'; object-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; report-uri @PwmContextPath@/public/command/cspReport
 security.httpsServer.selfCert.futureSeconds=63113904
 security.httpsServer.selfCert.futureSeconds=63113904
 security.httpsServer.selfCert.alg=RSA
 security.httpsServer.selfCert.alg=RSA

+ 65 - 0
server/src/main/resources/password/pwm/DomainProperty.properties

@@ -0,0 +1,65 @@
+#
+# Password Management Servlets (PWM)
+# http://www.pwm-project.org
+#
+# Copyright (c) 2006-2009 Novell, Inc.
+# Copyright (c) 2009-2021 The PWM Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+# Default domain values.  This is not an end user modifiable file.  Values
+# can be overridden in configuration at Settings -> Application -> App Property Overrides
+
+client.ajax.typingTimeout=20000
+client.ajax.typingWait=700
+http.cookie.default.secureFlag=auto
+http.cookie.httponly.enable=true
+http.cookie.theme.name=theme
+http.cookie.theme.age=604800
+http.cookie.locale.name=locale
+http.cookie.authRecord.name=authRecord
+http.cookie.authRecord.age=604800
+http.cookie.maxReadLength=10240
+http.cookie.captchaSkip.name=captcha-key
+http.cookie.captchaSkip.age=86400
+http.cookie.login.name=SESSION
+http.cookie.nonce.name=ID
+http.cookie.nonce.length=32
+http.cookie.sameSite.value=Strict
+ldap.resolveCanonicalDN=true
+ldap.cache.canonical.enable=true
+ldap.cache.canonical.seconds=600
+ldap.cache.userGuid.enable=true
+ldap.cache.userGuid.seconds=3600
+ldap.chaiSettings=
+ldap.proxy.connectionsPerProfile=10
+ldap.proxy.maxConnections=50
+ldap.proxy.idleThreadLocal.timeoutMS=90000
+ldap.extensions.nmas.enable=true
+ldap.connection.timeoutMS=30000
+ldap.profile.retryDelayMS=30000
+ldap.promiscuousEnable=false
+ldap.password.replicaCheck.initialDelayMS=1000
+ldap.password.replicaCheck.cycleDelayMS=7000
+ldap.password.change.self.enable=true
+ldap.password.change.helpdesk.enable=true
+ldap.guid.pattern=@UUID@
+ldap.browser.maxEntries=1000
+ldap.search.paging.enable=auto
+ldap.search.paging.size=500
+ldap.search.parallel.enable=true
+ldap.search.parallel.factor=5
+ldap.search.parallel.threadMax=50
+ldap.oracle.postTempPasswordUseCurrentTime=false

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

@@ -349,7 +349,7 @@ Setting_Description_display.showLoginPageOptions=Enable this option to have the
 Setting_Description_display.showSuccessPage=Enable this option to display a "success" page to the user informing the user the action completed successfully.  You can bypass this page by changing this setting to false.
 Setting_Description_display.showSuccessPage=Enable this option to display a "success" page to the user informing the user the action completed successfully.  You can bypass this page by changing this setting to false.
 Setting_Description_display.tokenSuccessPage=Enable this option to show a page to users after they enter their tokens successfully.
 Setting_Description_display.tokenSuccessPage=Enable this option to show a page to users after they enter their tokens successfully.
 Setting_Description_display.updateAttributes.agreement=<p>Specify a message to display to the users before allowing them to update their profiles. If blank, @PwmAppName@ does not display the update profile agreement page to the users. This message can include HTML tags.
 Setting_Description_display.updateAttributes.agreement=<p>Specify a message to display to the users before allowing them to update their profiles. If blank, @PwmAppName@ does not display the update profile agreement page to the users. This message can include HTML tags.
-Setting_Description_domain.list=List of domains supported by this application instance.  Domain order is unimportant.  The value of the domain(s) may be used in public URLs and parameters.<p>Domains provide a way for multiple systems/sites/tenants/customers to use a single instance of this @PwmAppName@ application.  Typically only a single instance is required.  If multiple domains are listed, the configuration editor will allow per-domain configuration of many settings.  Other settings are system-level and apply to the entire application instance.</p><p>Saving the configuration after increasing or decreasing the number of domains beyond a single domain may cause application URLs to change, and this configuration editor will change to allow editing of multiple domain configurations</p>
+Setting_Description_domain.list=<p>List of domains supported by this application instance.  Domain order is unimportant.  The value of the domain(s) may be used in public URLs and parameters.</p><p>Domains provide a way for multiple systems/sites/tenants/customers to use a single instance of this @PwmAppName@ application.  Typically only a single instance is required.  If multiple domains are listed, the configuration editor will allow per-domain configuration of many settings.  Other settings are system-level and apply to the entire application instance.</p><p>Saving the configuration after increasing or decreasing the number of domains beyond a single domain may cause application URLs to change, and this configuration editor will change to allow editing of multiple domain configurations.</p>
 Setting_Description_domain.hosts=A list of explicit fully qualified DNS hostnames to be used for this domain.  If this application is accessed by a client using an exact hostname specified here, then this domain will be used to service the client.    Example: "password.acme.com".
 Setting_Description_domain.hosts=A list of explicit fully qualified DNS hostnames to be used for this domain.  If this application is accessed by a client using an exact hostname specified here, then this domain will be used to service the client.    Example: "password.acme.com".
 Setting_Description_domain.system.adminDomain=Administrative Domain
 Setting_Description_domain.system.adminDomain=Administrative Domain
 Setting_Description_domain.system.domainPathsEnabled=If enabled, domain IDs will be added to the URL path used to access this application, and URL paths will require the inclusion of the domain ID in the path.  Example: "/pwm/private/login" will become "/pwm/default/private/login" or "/pwm/acme/private/login".<br/><br/>Regardless of this setting, the domain is always accessible if the host header (the url shown in the browser address bar) is matched by the setting in <code>@PwmSettingReference:domain.hosts@</code>.
 Setting_Description_domain.system.domainPathsEnabled=If enabled, domain IDs will be added to the URL path used to access this application, and URL paths will require the inclusion of the domain ID in the path.  Example: "/pwm/private/login" will become "/pwm/default/private/login" or "/pwm/acme/private/login".<br/><br/>Regardless of this setting, the domain is always accessible if the host header (the url shown in the browser address bar) is matched by the setting in <code>@PwmSettingReference:domain.hosts@</code>.

+ 24 - 0
server/src/test/java/password/pwm/AppPropertyTest.java

@@ -22,11 +22,17 @@ package password.pwm;
 
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
+import password.pwm.util.localdb.TestHelper;
 
 
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.ResourceBundle;
 import java.util.ResourceBundle;
 import java.util.Set;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 
 public class AppPropertyTest
 public class AppPropertyTest
 {
 {
@@ -51,6 +57,15 @@ public class AppPropertyTest
         }
         }
     }
     }
 
 
+    @Test
+    public void testDuplicateKeys()
+    {
+        TestHelper.testEnumAttributeUniqueness(
+                AppProperty.class,
+                appProperty -> List.of( appProperty.getKey() ),
+                "enum key" );
+    }
+
     @Test
     @Test
     public void testKeyValues()
     public void testKeyValues()
     {
     {
@@ -84,4 +99,13 @@ public class AppPropertyTest
                     + " does not have a corresponding resource bundle value" );
                     + " does not have a corresponding resource bundle value" );
         }
         }
     }
     }
+
+    static Map<String, String> resourceBundleToStringMap( final ResourceBundle resourceBundle )
+    {
+        return Collections.list( resourceBundle.getKeys() ).stream()
+                .collect( Collectors.toUnmodifiableMap(
+                        Function.identity(),
+                        resourceBundle::getString
+                ) );
+    }
 }
 }

+ 5 - 9
server/src/test/java/password/pwm/config/PwmSettingTest.java

@@ -35,11 +35,11 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.json.JsonFactory;
+import password.pwm.util.localdb.TestHelper;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.util.secure.PwmSecurityKey;
 
 
 import java.io.InputStream;
 import java.io.InputStream;
 import java.util.EnumSet;
 import java.util.EnumSet;
-import java.util.HashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
@@ -203,14 +203,10 @@ public class PwmSettingTest
     @Test
     @Test
     public void testKeyUniqueness()
     public void testKeyUniqueness()
     {
     {
-        final Set<String> seenKeys = new HashSet<>();
-        for ( final PwmSetting pwmSetting : PwmSetting.values() )
-        {
-            // duplicate key found
-            Assertions.assertFalse( seenKeys.contains( pwmSetting.getKey() ) );
-            seenKeys.add( pwmSetting.getKey() );
-        }
-        Assertions.assertEquals( seenKeys.size(), PwmSetting.values().length );
+        TestHelper.testEnumAttributeUniqueness(
+                PwmSetting.class,
+                pwmSetting -> List.of( pwmSetting.getKey() ),
+                "enum key" );
     }
     }
 
 
     @Test
     @Test

+ 7 - 13
server/src/test/java/password/pwm/error/PwmErrorTest.java

@@ -22,25 +22,19 @@ package password.pwm.error;
 
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
+import password.pwm.util.localdb.TestHelper;
 
 
-import java.util.HashSet;
-import java.util.Set;
+import java.util.List;
 
 
 public class PwmErrorTest
 public class PwmErrorTest
 {
 {
-
     @Test
     @Test
-    public void testPwmErrorNumbers() throws Exception
+    public void testPwmErrorNumbers()
     {
     {
-        final Set<Integer> seenErrorNumbers = new HashSet<>();
-        for ( final PwmError loopError : PwmError.values() )
-        {
-            if ( seenErrorNumbers.contains( loopError.getErrorCode() ) )
-            {
-                throw new Exception( "duplicate error code: " + loopError.getErrorCode() + " " + loopError.toString() );
-            }
-            seenErrorNumbers.add( loopError.getErrorCode() );
-        }
+        TestHelper.testEnumAttributeUniqueness(
+                PwmError.class,
+                pwmError -> List.of( pwmError.getErrorCode() ),
+                "error number" );
     }
     }
 
 
     @Test
     @Test

+ 6 - 10
server/src/test/java/password/pwm/health/HealthMessageTest.java

@@ -20,17 +20,16 @@
 
 
 package password.pwm.health;
 package password.pwm.health;
 
 
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.AppConfig;
 import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.localdb.TestHelper;
 
 
-import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
-import java.util.Set;
 
 
 public class HealthMessageTest
 public class HealthMessageTest
 {
 {
@@ -38,13 +37,10 @@ public class HealthMessageTest
     @Test
     @Test
     public void testHealthMessageUniqueKeys()
     public void testHealthMessageUniqueKeys()
     {
     {
-        final Set<String> seenKeys = new HashSet<>();
-        for ( final HealthMessage healthMessage : HealthMessage.values() )
-        {
-            // duplicate key found
-            Assertions.assertFalse( seenKeys.contains( healthMessage.getKey() ) );
-            seenKeys.add( healthMessage.getKey() );
-        }
+        TestHelper.testEnumAttributeUniqueness(
+                HealthMessage.class,
+                healthMessage -> List.of( healthMessage.getKey() ),
+                "key" );
     }
     }
 
 
     @Test
     @Test

+ 29 - 0
server/src/test/java/password/pwm/http/ServletTest.java

@@ -28,11 +28,15 @@ import org.reflections.util.ClasspathHelper;
 import org.reflections.util.ConfigurationBuilder;
 import org.reflections.util.ConfigurationBuilder;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.localdb.TestHelper;
 
 
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServlet;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 public class ServletTest
 public class ServletTest
@@ -62,6 +66,31 @@ public class ServletTest
         }
         }
     }
     }
 
 
+    @Test
+    public void testDuplicateServletNames2()
+    {
+        TestHelper.testAttributeUniqueness(
+                getServletClasses(),
+                servletClass ->
+                        Optional.ofNullable( servletClass.getAnnotation( WebServlet.class ) )
+                                .map( WebServlet::name )
+                                .map( name -> StringUtil.isTrimEmpty( name ) ? List.of() : List.of( name ) )
+                                .orElse( List.of() ),
+                "servlet name" );
+    }
+
+    @Test
+    public void testDuplicatePatternsNames2()
+    {
+        TestHelper.testAttributeUniqueness(
+                getServletClasses(),
+                servletClass ->
+                        Optional.ofNullable( servletClass.getAnnotation( WebServlet.class ) )
+                                .map( annot -> Arrays.asList( annot.urlPatterns() ) )
+                                .orElse( List.of() ),
+                "servlet urlPatterns" );
+    }
+
     @Test
     @Test
     public void testDuplicatePatternsNames()
     public void testDuplicatePatternsNames()
     {
     {

+ 21 - 0
server/src/test/java/password/pwm/http/servlet/ControlledPwmServletTest.java

@@ -30,6 +30,7 @@ import password.pwm.PwmConstants;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.localdb.TestHelper;
 
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Method;
@@ -41,6 +42,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
@@ -131,6 +133,25 @@ public class ControlledPwmServletTest
         }
         }
     }
     }
 
 
+    @Test
+    public void testDuplicateActionHandlers()
+    {
+        final Map<Class<? extends ControlledPwmServlet>, Map<String, Method>> dataMap = getClassAndMethods();
+
+        for ( final Class<? extends ControlledPwmServlet> controlledPwmServlet : dataMap.keySet() )
+        {
+            TestHelper.testAttributeUniqueness(
+                    List.of( controlledPwmServlet ),
+                    servlet -> JavaHelper.getAllMethodsForClass( controlledPwmServlet ).stream()
+                            .map( method -> method.getAnnotation( ControlledPwmServlet.ActionHandler.class ) )
+                            .filter( Objects::nonNull )
+                            .map( ControlledPwmServlet.ActionHandler::action )
+                            .toList(),
+                    "action handler attribution" );
+        }
+    }
+
+
     @Test
     @Test
     public void testActionHandlerMethodNaming()
     public void testActionHandlerMethodNaming()
     {
     {

+ 37 - 0
server/src/test/java/password/pwm/util/localdb/TestHelper.java

@@ -35,6 +35,11 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogLevel;
 
 
 import java.nio.file.Path;
 import java.nio.file.Path;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Function;
 
 
 public class TestHelper
 public class TestHelper
 {
 {
@@ -61,4 +66,36 @@ public class TestHelper
 
 
         return PwmApplication.createPwmApplication( pwmEnvironment );
         return PwmApplication.createPwmApplication( pwmEnvironment );
     }
     }
+
+    public static <C, R> void testAttributeUniqueness(
+            final Collection<C> collection,
+            final Function<C, Collection<R>> attributeExtractor,
+            final String attributeDebugLabel
+    )
+    {
+        final Set<R> seenAttributes = new HashSet<>();
+        for ( final C item : collection )
+        {
+            final Collection<R> attributes = attributeExtractor.apply( item );
+            for ( final R attribute : attributes )
+            {
+                if ( seenAttributes.contains( attribute ) )
+                {
+                    throw new IllegalStateException( "item " + item
+                            + " contains duplicate " + attributeDebugLabel + " value "
+                            + attribute );
+                }
+                seenAttributes.add( attribute );
+            }
+        }
+    }
+
+    public static <E extends Enum<E>, R> void testEnumAttributeUniqueness(
+            final Class<E> enumClass,
+            final Function<E, Collection<R>> attributeExtractor,
+            final String attributeDebugLabel
+    )
+    {
+        testAttributeUniqueness( EnumSet.allOf( enumClass ), attributeExtractor, attributeDebugLabel );
+    }
 }
 }

+ 4 - 4
webapp/src/main/webapp/WEB-INF/web.xml

@@ -119,10 +119,6 @@
         <url-pattern>/proxyCallback</url-pattern>
         <url-pattern>/proxyCallback</url-pattern>
     </filter-mapping>
     </filter-mapping>
     End CAS Config -->
     End CAS Config -->
-    <filter>
-        <filter-name>CookieUpdateFilter</filter-name>
-        <filter-class>password.pwm.http.filter.CookieManagementFilter</filter-class>
-    </filter>
     <filter>
     <filter>
         <filter-name>DomainInitFilter</filter-name>
         <filter-name>DomainInitFilter</filter-name>
         <filter-class>password.pwm.http.filter.DomainInitFilter</filter-class>
         <filter-class>password.pwm.http.filter.DomainInitFilter</filter-class>
@@ -131,6 +127,10 @@
         <filter-name>RequestInitializationFilter</filter-name>
         <filter-name>RequestInitializationFilter</filter-name>
         <filter-class>password.pwm.http.filter.RequestInitializationFilter</filter-class>
         <filter-class>password.pwm.http.filter.RequestInitializationFilter</filter-class>
     </filter>
     </filter>
+    <filter>
+        <filter-name>CookieUpdateFilter</filter-name>
+        <filter-class>password.pwm.http.filter.CookieManagementFilter</filter-class>
+    </filter>
     <filter>
     <filter>
         <filter-name>ObsoleteUrlFilter</filter-name>
         <filter-name>ObsoleteUrlFilter</filter-name>
         <filter-class>password.pwm.http.filter.ObsoleteUrlFilter</filter-class>
         <filter-class>password.pwm.http.filter.ObsoleteUrlFilter</filter-class>