Browse Source

refactoring - userkey obfuscation, jsp tags, http request and session management

Jason Rivard 2 years ago
parent
commit
7975a8dbee
100 changed files with 1668 additions and 1230 deletions
  1. 4 0
      data-service/src/main/java/password/pwm/receiver/SummaryBean.java
  2. 13 0
      data-service/src/main/webapp/WEB-INF/jsp/telemetry-viewer.jsp
  3. 31 0
      lib-data/src/main/java/password/pwm/data/FileUploadItem.java
  4. 8 0
      lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java
  5. 5 3
      lib-util/src/main/java/password/pwm/util/java/JavaHelper.java
  6. 2 0
      server/src/main/java/password/pwm/PwmConstants.java
  7. 5 5
      server/src/main/java/password/pwm/PwmDomain.java
  8. 67 27
      server/src/main/java/password/pwm/PwmDomainUtil.java
  9. 1 1
      server/src/main/java/password/pwm/bean/DomainID.java
  10. 0 82
      server/src/main/java/password/pwm/bean/UserIdentity.java
  11. 7 2
      server/src/main/java/password/pwm/config/AppConfig.java
  12. 1 1
      server/src/main/java/password/pwm/config/PwmSetting.java
  13. 2 2
      server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java
  14. 9 0
      server/src/main/java/password/pwm/config/stored/StoredConfigKey.java
  15. 1 1
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  16. 5 5
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  17. 1 18
      server/src/main/java/password/pwm/http/ContextManager.java
  18. 2 2
      server/src/main/java/password/pwm/http/HttpEventManager.java
  19. 23 0
      server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java
  20. 50 132
      server/src/main/java/password/pwm/http/PwmRequest.java
  21. 205 0
      server/src/main/java/password/pwm/http/PwmRequestLocaleResolver.java
  22. 260 0
      server/src/main/java/password/pwm/http/PwmRequestUtil.java
  23. 1 5
      server/src/main/java/password/pwm/http/PwmResponse.java
  24. 7 136
      server/src/main/java/password/pwm/http/PwmSession.java
  25. 11 10
      server/src/main/java/password/pwm/http/PwmSessionFactory.java
  26. 3 3
      server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java
  27. 1 1
      server/src/main/java/password/pwm/http/filter/ApplicationModeFilter.java
  28. 1 1
      server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java
  29. 14 112
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  30. 16 30
      server/src/main/java/password/pwm/http/filter/SessionFilter.java
  31. 15 25
      server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java
  32. 5 5
      server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  33. 3 3
      server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  34. 1 2
      server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java
  35. 5 5
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java
  36. 3 3
      server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java
  37. 3 5
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  38. 11 13
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  39. 7 5
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java
  40. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/DomainStateReader.java
  41. 2 2
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java
  42. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/AbstractUriCertImportFunction.java
  43. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java
  44. 3 3
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  45. 5 5
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java
  46. 3 3
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java
  47. 1 1
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  48. 7 7
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  49. 3 3
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java
  50. 1 1
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskCardInfoBean.java
  51. 1 1
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java
  52. 31 41
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  53. 27 4
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java
  54. 4 4
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  55. 3 3
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  56. 2 4
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java
  57. 17 12
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  58. 40 4
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  59. 1 1
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java
  60. 77 0
      server/src/main/java/password/pwm/http/servlet/resource/TextFileResource.java
  61. 3 17
      server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java
  62. 83 0
      server/src/main/java/password/pwm/http/tag/PwmTextFileTag.java
  63. 3 0
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java
  64. 8 1
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTag.java
  65. 29 1
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  66. 21 248
      server/src/main/java/password/pwm/http/tag/value/PwmValue.java
  67. 2 3
      server/src/main/java/password/pwm/http/tag/value/PwmValueHandler.java
  68. 256 0
      server/src/main/java/password/pwm/http/tag/value/PwmValueHandlers.java
  69. 3 3
      server/src/main/java/password/pwm/ldap/LdapBrowser.java
  70. 1 3
      server/src/main/java/password/pwm/ldap/LdapDomainService.java
  71. 7 7
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  72. 7 7
      server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java
  73. 3 3
      server/src/main/java/password/pwm/ldap/permission/UserPermissionUtility.java
  74. 6 6
      server/src/main/java/password/pwm/ldap/search/UserSearchJob.java
  75. 44 27
      server/src/main/java/password/pwm/ldap/search/UserSearchResults.java
  76. 6 44
      server/src/main/java/password/pwm/ldap/search/UserSearchService.java
  77. 2 1
      server/src/main/java/password/pwm/svc/PwmServiceEnum.java
  78. 1 1
      server/src/main/java/password/pwm/svc/cr/NMASCrOperator.java
  79. 52 29
      server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java
  80. 12 9
      server/src/main/java/password/pwm/svc/intruder/IntruderServiceClient.java
  81. 1 8
      server/src/main/java/password/pwm/svc/report/ReportRecordLocalDBStorageService.java
  82. 2 2
      server/src/main/java/password/pwm/svc/report/ReportService.java
  83. 11 6
      server/src/main/java/password/pwm/svc/secure/AbstractSecureService.java
  84. 20 3
      server/src/main/java/password/pwm/svc/secure/SecureService.java
  85. 2 2
      server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java
  86. 1 0
      server/src/main/java/password/pwm/svc/stats/EpsStatistic.java
  87. 3 3
      server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java
  88. 1 1
      server/src/main/java/password/pwm/util/SampleDataGenerator.java
  89. 3 3
      server/src/main/java/password/pwm/util/cli/commands/ExportResponsesCommand.java
  90. 3 3
      server/src/main/java/password/pwm/util/cli/commands/ResponseStatsCommand.java
  91. 1 1
      server/src/main/java/password/pwm/util/debug/DebugItemGenerator.java
  92. 1 1
      server/src/main/java/password/pwm/util/debug/LdapConnectionsDebugItemGenerator.java
  93. 3 3
      server/src/main/java/password/pwm/util/form/FormUtility.java
  94. 1 2
      server/src/main/java/password/pwm/util/java/MiscUtil.java
  95. 1 1
      server/src/main/java/password/pwm/util/logging/PwmLogManager.java
  96. 4 2
      server/src/main/java/password/pwm/util/logging/PwmLogger.java
  97. 2 2
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  98. 4 4
      server/src/main/java/password/pwm/util/password/RandomPasswordGenerator.java
  99. 3 3
      server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java
  100. 12 23
      server/src/main/java/password/pwm/ws/server/RestServlet.java

+ 4 - 0
data-service/src/main/java/password/pwm/receiver/SummaryBean.java

@@ -45,6 +45,7 @@ public class SummaryBean
     private Map<String, Integer> settingCount;
     private Map<String, Integer> statCount;
     private Map<String, Integer> osCount;
+    private Map<String, Integer> deploymentCount;
     private Map<String, Integer> dbCount;
     private Map<String, Integer> javaCount;
     private Map<String, Integer> appVersionCount;
@@ -60,6 +61,7 @@ public class SummaryBean
         final Map<String, Integer> appServerCount = new TreeMap<>();
         final Map<String, Integer> settingCount = new TreeMap<>();
         final Map<String, Integer> statCount = new TreeMap<>();
+        final Map<String, Integer> deploymentCount = new TreeMap<>();
         final Map<String, Integer> osCount = new TreeMap<>();
         final Map<String, Integer> dbCount = new TreeMap<>();
         final Map<String, Integer> javaCount = new TreeMap<>();
@@ -106,6 +108,8 @@ public class SummaryBean
 
                 incrementCounterMap( javaCount, siteSummary.getJavaVm() );
 
+                incrementCounterMap( deploymentCount, bean.getAbout().get( PwmAboutProperty.app_deployment_type.name() ) );
+
                 incrementCounterMap( appVersionCount, siteSummary.getVersion() );
 
                 for ( final String settingKey : bean.getConfiguredSettings() )

+ 13 - 0
data-service/src/main/webapp/WEB-INF/jsp/telemetry-viewer.jsp

@@ -116,6 +116,19 @@
         </tr>
         <% } %>
     </table>
+    <h2>Deployment Type</h2>
+    <table class="sortable">
+        <tr>
+            <td><b>Deployment Type</b></td>
+            <td><b>Count</b></td>
+        </tr>
+        <% for (final String osName : summaryBean.getDeploymentCount().keySet()) { %>
+        <tr>
+            <td><%=osName%></td>
+            <td><%=summaryBean.getDeploymentCount().get(osName)%></td>
+        </tr>
+        <% } %>
+    </table>
     <h2>DB Vendors</h2>
     <table class="sortable">
         <tr>

+ 31 - 0
lib-data/src/main/java/password/pwm/data/FileUploadItem.java

@@ -0,0 +1,31 @@
+/*
+ * 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.data;
+
+import lombok.Value;
+
+@Value
+public class FileUploadItem
+{
+    private final String name;
+    private final String type;
+    private final ImmutableByteArray content;
+}

+ 8 - 0
lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java

@@ -27,6 +27,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -223,4 +224,11 @@ public class CollectionUtil
     {
         return EnumSet.allOf( enumClass ).stream();
     }
+
+    public static <T> Set<T> setUnion( final Set<T> set1, final Set<T> set2 )
+    {
+        final Set<T> s = new HashSet<>( set1 == null ? Collections.emptySet() : set1 );
+        s.retainAll( set2 == null ? Collections.<T>emptySet() : set2 );
+        return Set.copyOf( s );
+    }
 }

+ 5 - 3
lib-util/src/main/java/password/pwm/util/java/JavaHelper.java

@@ -58,11 +58,11 @@ import java.util.stream.Collectors;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
 
-public class JavaHelper
+public final class JavaHelper
 {
     private static final char[] HEX_CHAR_ARRAY = "0123456789ABCDEF".toCharArray();
 
-    private JavaHelper( )
+    private JavaHelper()
     {
     }
 
@@ -310,6 +310,7 @@ public class JavaHelper
 
     /**
      * Copy of {@link ThreadInfo#toString()} but with the MAX_FRAMES changed from 8 to 256.
+     *
      * @param threadInfo thread information
      * @return a stacktrace string with newline formatting
      */
@@ -520,7 +521,7 @@ public class JavaHelper
     public static byte[] gunzip( final byte[] bytes )
             throws IOException
     {
-        try (  GZIPInputStream inputGzipStream = new GZIPInputStream( new ByteArrayInputStream( bytes ) ) )
+        try ( GZIPInputStream inputGzipStream = new GZIPInputStream( new ByteArrayInputStream( bytes ) ) )
         {
             return inputGzipStream.readAllBytes();
         }
@@ -547,6 +548,7 @@ public class JavaHelper
 
     /**
      * Append multiple byte array values into a single array.
+     *
      * @param byteArrays two or more byte arrays.
      * @return A new array with the contents of all byteArrays appended
      */

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

@@ -111,6 +111,8 @@ public abstract class PwmConstants
     public static final String REQUEST_ATTR_FORGOTTEN_PW_AVAIL_TOKEN_DEST_CACHE = "ForgottenPw-AvailableTokenDestCache";
     public static final String REQUEST_ATTR_DOMAIN = "domain";
     public static final String REQUEST_ATTR_PWM_APPLICATION = "PwmApplication";
+    public static final String REQUEST_ATTR_SRC_ADDRESS = "SourceAddress";
+    public static final String REQUEST_ATTR_SRC_HOSTNAME = "SourceAddress";
 
     public static final String LOG_REMOVED_VALUE_REPLACEMENT = readPwmConstantsBundle( "log.removedValue" );
 

+ 5 - 5
server/src/main/java/password/pwm/PwmDomain.java

@@ -33,7 +33,7 @@ import password.pwm.http.servlet.peoplesearch.PeopleSearchService;
 import password.pwm.http.servlet.resource.ResourceServletService;
 import password.pwm.http.state.SessionStateService;
 import password.pwm.ldap.LdapDomainService;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmServiceEnum;
 import password.pwm.svc.PwmServiceManager;
@@ -159,7 +159,7 @@ public class PwmDomain
         return pwmApplication.determineIfDetailErrorMsgShown();
     }
 
-    public LdapDomainService getLdapConnectionService( )
+    public LdapDomainService getLdapService( )
     {
         return ( LdapDomainService ) pwmServiceManager.getService( PwmServiceEnum.LdapConnectionService );
     }
@@ -191,7 +191,7 @@ public class PwmDomain
     public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final String profileId )
             throws PwmUnrecoverableException
     {
-        return getLdapConnectionService().getProxyChaiProvider( sessionLabel, profileId );
+        return getLdapService().getProxyChaiProvider( sessionLabel, profileId );
     }
 
     public List<PwmService> getPwmServices( )
@@ -199,9 +199,9 @@ public class PwmDomain
         return pwmServiceManager.getRunningServices();
     }
 
-    public UserSearchEngine getUserSearchEngine()
+    public UserSearchService getUserSearchEngine()
     {
-        return ( UserSearchEngine ) pwmServiceManager.getService( PwmServiceEnum.UserSearchEngine );
+        return ( UserSearchService ) pwmServiceManager.getService( PwmServiceEnum.UserSearchEngine );
     }
 
     public HttpClientService getHttpClientService()

+ 67 - 27
server/src/main/java/password/pwm/PwmDomainUtil.java

@@ -22,7 +22,8 @@ package password.pwm;
 
 import password.pwm.bean.DomainID;
 import password.pwm.config.AppConfig;
-import password.pwm.config.DomainConfig;
+import password.pwm.config.stored.StoredConfigKey;
+import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.TimeDuration;
@@ -31,11 +32,9 @@ import password.pwm.util.logging.PwmLogger;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
@@ -136,7 +135,7 @@ class PwmDomainUtil
                         + "' configuration modification detected as: " + modifyCategory ) ) );
 
         final Set<PwmDomain> deletedDomains = pwmApplication.domains().entrySet().stream()
-                .filter( e -> categorizedDomains.get( DomainModifyCategory.obsolete ).contains( e.getKey() ) )
+                .filter( e -> categorizedDomains.get( DomainModifyCategory.removed ).contains( e.getKey() ) )
                 .map( Map.Entry::getValue ).collect( Collectors.toSet() );
 
 
@@ -196,49 +195,90 @@ class PwmDomainUtil
 
     enum DomainModifyCategory
     {
-        obsolete,
+        removed,
         unchanged,
         modified,
         created,
     }
 
-    private static Map<DomainModifyCategory, Set<DomainID>> categorizeDomainModifications(
+    public static Map<DomainModifyCategory, Set<DomainID>> categorizeDomainModifications(
             final AppConfig newConfig,
             final AppConfig oldConfig
     )
     {
-        final Map<DomainModifyCategory, Set<DomainID>> types = new EnumMap<>( DomainModifyCategory.class );
 
         {
-            final Set<DomainID> obsoleteDomains = new HashSet<>( oldConfig.getDomainConfigs().keySet() );
-            obsoleteDomains.removeAll( newConfig.getDomainConfigs().keySet() );
-            types.put( DomainModifyCategory.obsolete, CollectionUtil.stripNulls( obsoleteDomains ) );
+            final Instant newInstant = newConfig.getStoredConfiguration().modifyTime();
+            final Instant oldInstant = oldConfig.getStoredConfiguration().modifyTime();
+            if ( newInstant != null && oldInstant != null && newInstant.isBefore( oldInstant ) )
+            {
+                throw new IllegalStateException( "refusing request to categorize changes due to oldConfig being newer than new config" );
+            }
         }
 
+        final Set<StoredConfigKey> modifiedValues = StoredConfigurationUtil.changedValues( newConfig.getStoredConfiguration(), oldConfig.getStoredConfiguration() );
+
+        return CATEGORIZERS.entrySet().stream()
+                .collect( Collectors.toUnmodifiableMap(
+                        Map.Entry::getKey,
+                        entry -> entry.getValue().categorize( newConfig, oldConfig, modifiedValues )
+                ) );
+    }
+
+    interface DomainModificationCategorizer
+    {
+        Set<DomainID> categorize( AppConfig newConfig, AppConfig oldConfig, Set<StoredConfigKey> modifiedValues );
+    }
+
+    private static final Map<DomainModifyCategory, DomainModificationCategorizer> CATEGORIZERS = Map.of(
+            DomainModifyCategory.removed, new RemovalCategorizer(),
+            DomainModifyCategory.created, new CreationCategorizer(),
+            DomainModifyCategory.unchanged, new UnchangedCategorizer(),
+            DomainModifyCategory.modified, new ModifiedCategorizer() );
+
+    private static class RemovalCategorizer implements DomainModificationCategorizer
+    {
+        @Override
+        public Set<DomainID> categorize( final AppConfig newConfig, final AppConfig oldConfig, final Set<StoredConfigKey> modifiedValues )
+        {
+            final Set<DomainID> removedDomains = new HashSet<>( oldConfig.getDomainConfigs().keySet() );
+            removedDomains.removeAll( newConfig.getDomainConfigs().keySet() );
+            return CollectionUtil.stripNulls( removedDomains );
+        }
+    }
+
+    private static class CreationCategorizer implements DomainModificationCategorizer
+    {
+        @Override
+        public Set<DomainID> categorize( final AppConfig newConfig, final AppConfig oldConfig, final Set<StoredConfigKey> modifiedValues )
         {
             final Set<DomainID> createdDomains = new HashSet<>( newConfig.getDomainConfigs().keySet() );
             createdDomains.removeAll( oldConfig.getDomainConfigs().keySet() );
-            types.put( DomainModifyCategory.created, CollectionUtil.stripNulls( createdDomains ) );
+            return CollectionUtil.stripNulls( createdDomains );
         }
+    }
 
-        final Set<DomainID> unchangedDomains = new HashSet<>();
-        final Set<DomainID> modifiedDomains = new HashSet<>();
-        for ( final DomainID domainID : newConfig.getDomainConfigs().keySet() )
+    private static class UnchangedCategorizer implements DomainModificationCategorizer
+    {
+        @Override
+        public Set<DomainID> categorize( final AppConfig newConfig, final AppConfig oldConfig, final Set<StoredConfigKey> modifiedValues )
         {
-            final DomainConfig newDomainConfig = newConfig.getDomainConfigs().get( domainID );
-            final DomainConfig oldDomainConfig = oldConfig.getDomainConfigs().get( newDomainConfig.getDomainID() );
+            final Set<DomainID> persistentDomains = new HashSet<>( CollectionUtil.setUnion( newConfig.getDomainConfigs().keySet(), oldConfig.getDomainConfigs().keySet() ) );
+            persistentDomains.removeAll( StoredConfigKey.uniqueDomains( modifiedValues ) );
+            return Set.copyOf( persistentDomains );
+        }
+    }
 
-            if ( newDomainConfig != null && oldDomainConfig != null && Objects.equals( oldDomainConfig.getValueHash(), newDomainConfig.getValueHash() ) )
-            {
-                unchangedDomains.add( domainID );
-            }
-            else
-            {
-                modifiedDomains.add( domainID );
-            }
+    private static class ModifiedCategorizer implements DomainModificationCategorizer
+    {
+        @Override
+        public Set<DomainID> categorize( final AppConfig newConfig, final AppConfig oldConfig, final Set<StoredConfigKey> modifiedValues )
+        {
+            final Set<DomainID> persistentDomains = new HashSet<>( CollectionUtil.setUnion( newConfig.getDomainConfigs().keySet(), oldConfig.getDomainConfigs().keySet() ) );
+            persistentDomains.retainAll( StoredConfigKey.uniqueDomains( modifiedValues ) );
+            return Set.copyOf( persistentDomains );
         }
-        types.put( DomainModifyCategory.unchanged, CollectionUtil.stripNulls( unchangedDomains ) );
-        types.put( DomainModifyCategory.modified, CollectionUtil.stripNulls( modifiedDomains ) );
-        return Collections.unmodifiableMap( types );
     }
+
+
 }

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

@@ -30,7 +30,7 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
 
-public class DomainID implements Comparable<DomainID>, Serializable
+public final class DomainID implements Comparable<DomainID>, Serializable
 {
     public static final List<String> DOMAIN_RESERVED_WORDS = List.of( "system", "private", "public", "pwm", "sspr", "domain", "profile", "password" );
     public static final DomainID DOMAIN_ID_DEFAULT = create( "default" );

+ 0 - 82
server/src/main/java/password/pwm/bean/UserIdentity.java

@@ -30,13 +30,8 @@ import password.pwm.config.profile.LdapProfile;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.svc.cache.CacheKey;
-import password.pwm.svc.cache.CachePolicy;
-import password.pwm.svc.cache.CacheService;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.json.JsonFactory;
-import password.pwm.util.java.StringUtil;
-import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.Serializable;
@@ -130,41 +125,6 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         return toDisplayString();
     }
 
-    public String toObfuscatedKey( final PwmApplication pwmApplication )
-            throws PwmUnrecoverableException
-    {
-        // use local cache first.
-        if ( StringUtil.notEmpty( obfuscatedValue ) )
-        {
-            return obfuscatedValue;
-        }
-
-        // check app cache.  This is used primarily so that keys are static over some meaningful lifetime, allowing browser caching based on keys.
-        final CacheService cacheService = pwmApplication.getCacheService();
-        final CacheKey cacheKey = CacheKey.newKey( this.getClass(), this, "obfuscatedKey" );
-        final String cachedValue = cacheService.get( cacheKey, String.class );
-
-        if ( StringUtil.notEmpty( cachedValue ) )
-        {
-            obfuscatedValue = cachedValue;
-            return cachedValue;
-        }
-
-        // generate key
-        try
-        {
-            final String jsonValue = JsonFactory.get().serialize( this );
-            final String localValue = CRYPO_HEADER + pwmApplication.getSecureService().encryptToString( jsonValue );
-            this.obfuscatedValue = localValue;
-            cacheService.put( cacheKey, CachePolicy.makePolicyWithExpiration( TimeDuration.DAY ), localValue );
-            return localValue;
-        }
-        catch ( final Exception e )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected error making obfuscated user key: " + e.getMessage() ) );
-        }
-    }
-
     public String toDelimitedKey( )
     {
         return JsonFactory.get().serialize( this );
@@ -177,29 +137,6 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
                 + ( ( this.getLdapProfileID() != null && !this.getLdapProfileID().isEmpty() ) ? " (" + this.getLdapProfileID() + ")" : "" );
     }
 
-    public static UserIdentity fromObfuscatedKey( final String key, final PwmApplication pwmApplication )
-            throws PwmUnrecoverableException
-    {
-        Objects.requireNonNull( pwmApplication );
-        JavaHelper.requireNonEmpty( key, "key can not be null or empty" );
-
-        if ( !key.startsWith( CRYPO_HEADER ) )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "cannot reverse obfuscated user key: missing header; value=" + key ) );
-        }
-
-        try
-        {
-            final String input = key.substring( CRYPO_HEADER.length() );
-            final String jsonValue = pwmApplication.getSecureService().decryptStringValue( input );
-            return JsonFactory.get().deserialize( jsonValue, UserIdentity.class );
-        }
-        catch ( final Exception e )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected error reversing obfuscated user key: " + e.getMessage() ) );
-        }
-    }
-
     public static UserIdentity fromDelimitedKey( final SessionLabel sessionLabel, final String key )
             throws PwmUnrecoverableException
     {
@@ -247,25 +184,6 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         return create( userDN, profileID, domainID );
     }
 
-    /**
-     * Attempt to de-serialize value using delimited or obfuscated key.
-     *
-     * @deprecated  Should be used by calling {@link #fromDelimitedKey(String)} or {@link #fromObfuscatedKey(String, PwmApplication)}.
-     */
-    @Deprecated
-    public static UserIdentity fromKey( final SessionLabel sessionLabel, final String key, final PwmApplication pwmApplication )
-            throws PwmUnrecoverableException
-    {
-        JavaHelper.requireNonEmpty( key );
-
-        if ( key.startsWith( CRYPO_HEADER ) )
-        {
-            return fromObfuscatedKey( key, pwmApplication );
-        }
-
-        return fromDelimitedKey( sessionLabel, key );
-    }
-
     public boolean canonicalEquals( final SessionLabel sessionLabel, final UserIdentity otherIdentity, final PwmApplication pwmApplication )
             throws PwmUnrecoverableException
     {

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

@@ -85,7 +85,7 @@ public class AppConfig implements SettingReader
     {
         try
         {
-            return new AppConfig( StoredConfigurationFactory.newConfig() );
+            return forStoredConfig( StoredConfigurationFactory.newConfig() );
         }
         catch ( final PwmUnrecoverableException e )
         {
@@ -98,7 +98,7 @@ public class AppConfig implements SettingReader
         return DEFAULT_CONFIG.get();
     }
 
-    public AppConfig( final StoredConfiguration storedConfiguration )
+    private AppConfig( final StoredConfiguration storedConfiguration )
     {
         this.storedConfiguration = storedConfiguration;
         this.settingReader = new StoredSettingReader( storedConfiguration, null, DomainID.systemId() );
@@ -118,6 +118,11 @@ public class AppConfig implements SettingReader
                         ( domainID ) -> new DomainConfig( this, DomainID.create( domainID ) ) ) ) );
     }
 
+    public static AppConfig forStoredConfig( final StoredConfiguration storedConfiguration )
+    {
+        return new AppConfig( storedConfiguration );
+    }
+
     public Set<String> getDomainIDs()
     {
         return domainIDs;

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

@@ -71,7 +71,7 @@ public enum PwmSetting
     DOMAIN_SYSTEM_ADMIN(
             "domain.system.adminDomain", PwmSettingSyntax.STRING, PwmSettingCategory.DOMAINS ),
     DOMAIN_DOMAIN_PATHS(
-            "domain.system.domainPaths", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.DOMAINS ),
+            "domain.system.domainPathsEnabled", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.DOMAINS ),
 
     // application settings
     APP_PROPERTY_OVERRIDES(

+ 2 - 2
server/src/main/java/password/pwm/config/stored/ConfigurationFileManager.java

@@ -120,7 +120,7 @@ public class ConfigurationFileManager
             final StoredConfiguration newStoredConfig = this.storedConfiguration == null
                     ? StoredConfigurationFactory.newConfig()
                     : this.storedConfiguration;
-            domainConfig = new AppConfig( newStoredConfig );
+            domainConfig = AppConfig.forStoredConfig( newStoredConfig );
         }
         return domainConfig;
     }
@@ -210,7 +210,7 @@ public class ConfigurationFileManager
         int backupRotations = 0;
         if ( pwmApplication != null )
         {
-            final AppConfig domainConfig = new AppConfig( storedConfiguration );
+            final AppConfig domainConfig = AppConfig.forStoredConfig( storedConfiguration );
             final String backupDirSetting = domainConfig.readAppProperty( AppProperty.BACKUP_LOCATION );
             if ( backupDirSetting != null && backupDirSetting.length() > 0 )
             {

+ 9 - 0
server/src/main/java/password/pwm/config/stored/StoredConfigKey.java

@@ -35,6 +35,8 @@ import java.io.Serializable;
 import java.util.Comparator;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey>
 {
@@ -279,6 +281,13 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
         return getLabel( PwmConstants.DEFAULT_LOCALE );
     }
 
+    public static Set<DomainID> uniqueDomains( final Set<StoredConfigKey> storedConfigKeys )
+    {
+        return storedConfigKeys.stream()
+                .map( StoredConfigKey::getDomainID )
+                .collect( Collectors.toUnmodifiableSet() );
+    }
+
     public PwmSettingSyntax getSyntax()
     {
         switch ( getRecordType() )

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

@@ -201,7 +201,7 @@ public abstract class StoredConfigurationUtil
             return false;
         }
         final Optional<String> passwordHash = storedConfiguration.readConfigProperty( ConfigurationProperty.PASSWORD_HASH );
-        return passwordHash.isPresent() && BCrypt.testAnswer( password, passwordHash.get(), new AppConfig( storedConfiguration ) );
+        return passwordHash.isPresent() && BCrypt.testAnswer( password, passwordHash.get(), AppConfig.forStoredConfig( storedConfiguration ) );
     }
 
     public static boolean hasPassword( final StoredConfiguration storedConfiguration )

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

@@ -125,7 +125,7 @@ public class LDAPHealthChecker implements HealthSupplier
             returnRecords.addAll( profileRecords );
         }
 
-        for ( final Map.Entry<String, ErrorInformation> entry : pwmDomain.getLdapConnectionService().getLastLdapFailure().entrySet() )
+        for ( final Map.Entry<String, ErrorInformation> entry : pwmDomain.getLdapService().getLastLdapFailure().entrySet() )
         {
             final ErrorInformation errorInfo = entry.getValue();
             final LdapProfile ldapProfile = pwmDomain.getConfig().getLdapProfiles().get( entry.getKey() );
@@ -585,7 +585,7 @@ public class LDAPHealthChecker implements HealthSupplier
                         ldapProfile.getIdentifier(),
                         errorString.toString() ) );
 
-                pwmDomain.getLdapConnectionService().setLastLdapFailure( ldapProfile,
+                pwmDomain.getLdapService().setLastLdapFailure( ldapProfile,
                         new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, errorString.toString() ) );
                 return returnRecords;
             }
@@ -596,7 +596,7 @@ public class LDAPHealthChecker implements HealthSupplier
                         HealthMessage.LDAP_No_Connection,
                         e.getMessage() );
                 returnRecords.add( record );
-                pwmDomain.getLdapConnectionService().setLastLdapFailure( ldapProfile,
+                pwmDomain.getLdapService().setLastLdapFailure( ldapProfile,
                         new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, record.getDetail( PwmConstants.DEFAULT_LOCALE, pwmDomain.getConfig() ) ) );
                 return returnRecords;
             }
@@ -744,7 +744,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 final Collection<ChaiConfiguration> replicaConfigs = ChaiUtility.splitConfigurationPerReplica( profileChaiConfiguration, Collections.emptyMap() );
                 for ( final ChaiConfiguration chaiConfiguration : replicaConfigs )
                 {
-                    final ChaiProvider loopProvider = pwmDomain.getLdapConnectionService().getChaiProviderFactory().newProvider( chaiConfiguration );
+                    final ChaiProvider loopProvider = pwmDomain.getLdapService().getChaiProviderFactory().newProvider( chaiConfiguration );
                     replicaVendorMap.put( chaiConfiguration.getSetting( ChaiSetting.BIND_URLS ), loopProvider.getDirectoryVendor() );
                 }
             }
@@ -827,7 +827,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
                 for ( final ChaiConfiguration chaiConfiguration : replicaConfigs )
                 {
-                    final ChaiProvider loopProvider = pwmDomain.getLdapConnectionService().getChaiProviderFactory().newProvider( chaiConfiguration );
+                    final ChaiProvider loopProvider = pwmDomain.getLdapService().getChaiProviderFactory().newProvider( chaiConfiguration );
                     final ChaiEntry rootDSE = ChaiUtility.getRootDSE( loopProvider );
                     final Set<String> controls = rootDSE.readMultiStringAttribute( "supportedControl" );
                     final boolean asnSupported = controls.contains( PwmConstants.LDAP_AD_PASSWORD_POLICY_CONTROL_ASN );

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

@@ -46,7 +46,6 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PropertyConfigurationImporter;
 import password.pwm.util.PwmScheduler;
 import password.pwm.util.java.CollectionUtil;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.PwmTimeUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
@@ -56,7 +55,6 @@ import password.pwm.util.secure.X509Utils;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpSession;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -133,11 +131,6 @@ public class ContextManager implements Serializable
         return getContextManager( theContext ).getPwmApplication();
     }
 
-    public static ContextManager getContextManager( final HttpSession session ) throws PwmUnrecoverableException
-    {
-        return getContextManager( session.getServletContext() );
-    }
-
     public static ContextManager getContextManager( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
     {
         return getContextManager( pwmRequest.getHttpServletRequest().getServletContext() );
@@ -157,16 +150,6 @@ public class ContextManager implements Serializable
         return ( ContextManager ) theManager;
     }
 
-    public static String readEulaText( final ContextManager contextManager, final String filename )
-            throws IOException
-    {
-        final String path = PwmConstants.URL_PREFIX_PUBLIC + "/resources/text/" + filename;
-        final InputStream inputStream = contextManager.getResourceAsStream( path );
-        final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        JavaHelper.copyWhilePredicate( inputStream, byteArrayOutputStream, o -> true );
-        return byteArrayOutputStream.toString( PwmConstants.DEFAULT_CHARSET.name() );
-    }
-
     public PwmApplication getPwmApplication( )
             throws PwmUnrecoverableException
     {
@@ -733,7 +716,7 @@ public class ContextManager implements Serializable
         {
             LOGGER.trace( SESSION_LABEL, () -> "beginning auto-import ldap cert due to config property '"
                     + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() + "'" );
-            final AppConfig appConfig = new AppConfig( configReader.getStoredConfiguration() );
+            final AppConfig appConfig = AppConfig.forStoredConfig( configReader.getStoredConfiguration() );
             final StoredConfigurationModifier modifiedConfig = StoredConfigurationModifier.newModifier( configReader.getStoredConfiguration() );
 
             int importedCerts = 0;

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

@@ -68,7 +68,7 @@ public class HttpEventManager implements
         final HttpSession httpSession = httpSessionEvent.getSession();
         try
         {
-            final ContextManager contextManager = ContextManager.getContextManager( httpSession );
+            final ContextManager contextManager = ContextManager.getContextManager( httpSession.getServletContext() );
             final PwmApplication pwmApplication = contextManager.getPwmApplication();
             httpSession.setAttribute( PwmConstants.SESSION_ATTR_PWM_APP_NONCE, pwmApplication.getRuntimeNonce() );
 
@@ -102,7 +102,7 @@ public class HttpEventManager implements
                     {
                         pwmApplication.getSessionTrackService().removeSessionData( pwmSession );
                     }
-                    LOGGER.trace( pwmSession.getLabel(), () -> debugMsg );
+                    LOGGER.trace( () -> debugMsg );
                 }
                 else
                 {

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

@@ -31,6 +31,7 @@ import password.pwm.util.PasswordData;
 import password.pwm.util.Validator;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
@@ -50,6 +51,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class PwmHttpRequestWrapper
@@ -67,6 +69,12 @@ public class PwmHttpRequestWrapper
     private static final Set<String> HTTP_HEADER_DEBUG_STRIP_VALUES = Set.of(
                     HttpHeader.Authorization.getHttpName() );
 
+    private final Supplier<Optional<String>> srcHostnameSupplier = new LazySupplier<>(
+            () -> PwmRequestUtil.readUserHostname( this.getHttpServletRequest(), this.getAppConfig() ) );
+
+    private final Supplier<Optional<String>> srcAddressSupplier = new LazySupplier<>(
+            () -> PwmRequestUtil.readUserNetworkAddress( this.getHttpServletRequest(), this.getAppConfig() ) );
+
     public enum Flag
     {
         BypassValidation
@@ -303,6 +311,21 @@ public class PwmHttpRequestWrapper
         return Collections.unmodifiableList( result );
     }
 
+    public boolean hasSession()
+    {
+        return this.getHttpServletRequest().getSession( false ) != null;
+    }
+
+    public Optional<String> getSrcHostname()
+    {
+        return srcHostnameSupplier.get();
+    }
+
+    public Optional<String> getSrcAddress()
+    {
+        return srcAddressSupplier.get();
+    }
+
     public String readHeaderValueAsString( final HttpHeader headerName )
     {
         return readHeaderValueAsString( headerName.getHttpName() );

+ 50 - 132
server/src/main/java/password/pwm/http/PwmRequest.java

@@ -20,14 +20,12 @@
 
 package password.pwm.http;
 
-import lombok.Value;
 import org.apache.commons.fileupload.FileItemIterator;
 import org.apache.commons.fileupload.FileItemStream;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
-import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
@@ -49,16 +47,16 @@ import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.data.ImmutableByteArray;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.PwmRequestID;
 import password.pwm.http.servlet.PwmServletDefinition;
+import password.pwm.svc.secure.SecureService;
 import password.pwm.user.UserInfo;
 import password.pwm.util.Validator;
-import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogLevel;
@@ -75,7 +73,6 @@ import java.io.InputStream;
 import java.io.Serializable;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.EnumSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -85,25 +82,30 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Supplier;
 
 public class PwmRequest extends PwmHttpRequestWrapper
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmRequest.class );
 
+    private final PwmApplication pwmApplication;
     private final PwmResponse pwmResponse;
     private final PwmURL pwmURL;
     private final PwmRequestID pwmRequestID;
 
-    private final transient PwmApplication pwmApplication;
-    private final SessionLabel sessionLabel;
-    private final PwmRequestContext pwmRequestContext;
-
     private final Set<PwmRequestFlag> flags = EnumSet.noneOf( PwmRequestFlag.class );
     private final Instant requestStartTime = Instant.now();
     private final DomainID domainID;
     private final Lock cspCreationLock = new ReentrantLock();
 
-    private static final Lock CREATE_LOCK = new ReentrantLock();
+    private final Supplier<Locale> localeSupplier = new LazySupplier<>(
+            () -> PwmRequestLocaleResolver.resolveRequestLocale( this ) );
+
+    private final Supplier<PwmRequestContext> requestContextSupplier = new LazySupplier<>(
+            this::makePwmRequestContext );
+
+    private final Supplier<SessionLabel> sessionLabelSupplier = new LazySupplier<>(
+            () -> PwmRequestUtil.makeSessionLabel( this ) );
 
     public static PwmRequest forRequest(
             final HttpServletRequest request,
@@ -111,22 +113,14 @@ public class PwmRequest extends PwmHttpRequestWrapper
     )
             throws PwmUnrecoverableException
     {
-        CREATE_LOCK.lock();
-        try
-        {
-            PwmRequest pwmRequest = ( PwmRequest ) request.getAttribute( PwmRequestAttribute.PwmRequest.toString() );
-            if ( pwmRequest == null )
-            {
-                final PwmApplication pwmApplication = ContextManager.getPwmApplication( request );
-                pwmRequest = new PwmRequest( request, response, pwmApplication );
-                request.setAttribute( PwmRequestAttribute.PwmRequest.toString(), pwmRequest );
-            }
-            return pwmRequest;
-        }
-        finally
+        PwmRequest pwmRequest = ( PwmRequest ) request.getAttribute( PwmRequestAttribute.PwmRequest.toString() );
+        if ( pwmRequest == null )
         {
-            CREATE_LOCK.unlock();
+            final PwmApplication pwmApplication = ContextManager.getPwmApplication( request );
+            pwmRequest = new PwmRequest( request, response, pwmApplication );
+            request.setAttribute( PwmRequestAttribute.PwmRequest.toString(), pwmRequest );
         }
+        return pwmRequest;
     }
 
     private PwmRequest(
@@ -142,10 +136,9 @@ public class PwmRequest extends PwmHttpRequestWrapper
         this.pwmApplication = pwmApplication;
         this.pwmURL = PwmURL.create( this.getHttpServletRequest() );
         this.domainID = PwmHttpRequestWrapper.readDomainIdFromRequest( httpServletRequest );
-        this.sessionLabel = makeSessionLabel();
-        this.pwmRequestContext = makePwmRequestContext();
     }
 
+
     public PwmDomain getPwmDomain()
     {
         return pwmApplication.domains().get( getDomainID() );
@@ -153,29 +146,14 @@ public class PwmRequest extends PwmHttpRequestWrapper
 
     public PwmSession getPwmSession()
     {
-        return PwmSessionFactory.readPwmSession( this.getHttpServletRequest().getSession(), getPwmDomain() );
+        return PwmSessionFactory.readPwmSession( this, getPwmDomain() );
     }
 
     public SessionLabel getLabel()
     {
-        return sessionLabel;
+        return sessionLabelSupplier.get();
     }
 
-    private SessionLabel makeSessionLabel()
-    {
-        if ( getHttpServletRequest().getSession( false ) == null )
-        {
-            // in case session does not exist, invoked for some non-servlet requests such as logging
-            return SessionLabel.builder()
-                    .domain( domainID.stringValue() )
-                    .build();
-        }
-
-        // nominal case
-        return getPwmSession().getLabel().toBuilder()
-                .requestID( pwmRequestID.toString() )
-                .build();
-    }
 
     public PwmResponse getPwmResponse()
     {
@@ -184,17 +162,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
 
     public Locale getLocale()
     {
-        if ( isFlag( PwmRequestFlag.INCLUDE_CONFIG_CSS ) )
-        {
-            return PwmConstants.DEFAULT_LOCALE;
-        }
-        if ( !getURL().isLocalizable() )
-        {
-            return PwmConstants.DEFAULT_LOCALE;
-        }
-
-        final Locale userLocale = getPwmSession().getSessionStateBean().getLocale();
-        return userLocale != null ? userLocale : PwmConstants.DEFAULT_LOCALE;
+        return localeSupplier.get();
     }
 
     public void forwardToJsp( final JspUrl jspURL )
@@ -225,7 +193,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
         }
     }
 
-    public void outputJsonResult( final RestResultBean restResultBean )
+    public void outputJsonResult( final RestResultBean<?> restResultBean )
             throws IOException
     {
         this.getPwmResponse().outputJsonResult( restResultBean );
@@ -238,7 +206,6 @@ public class PwmRequest extends PwmHttpRequestWrapper
     }
 
     public Optional<InputStream> readFileUploadStream( final String filePartName )
-            throws IOException, ServletException, PwmUnrecoverableException
     {
         try
         {
@@ -267,55 +234,6 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return Optional.empty();
     }
 
-    public Map<String, FileUploadItem> readFileUploads(
-            final int maxFileSize,
-            final int maxItems
-    )
-            throws PwmUnrecoverableException
-    {
-        final Map<String, FileUploadItem> returnObj = new LinkedHashMap<>();
-        try
-        {
-            if ( ServletFileUpload.isMultipartContent( this.getHttpServletRequest() ) )
-            {
-                final ServletFileUpload upload = new ServletFileUpload();
-                final FileItemIterator iter = upload.getItemIterator( this.getHttpServletRequest() );
-                while ( iter.hasNext() && returnObj.size() < maxItems )
-                {
-                    final FileItemStream item = iter.next();
-                    final InputStream inputStream = item.openStream();
-                    final ImmutableByteArray fileContents = JavaHelper.copyToBytes( inputStream, maxFileSize + 1 );
-                    if ( fileContents.size() > maxFileSize )
-                    {
-                        final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "upload file size limit exceeded" );
-                        LOGGER.error( this, errorInformation );
-                        respondWithError( errorInformation );
-                        return Collections.emptyMap();
-                    }
-                    final FileUploadItem fileUploadItem = new FileUploadItem(
-                            item.getName(),
-                            item.getContentType(),
-                            fileContents
-                    );
-                    returnObj.put( item.getFieldName(), fileUploadItem );
-                }
-            }
-        }
-        catch ( final Exception e )
-        {
-            LOGGER.error( () -> "error reading file upload: " + e.getMessage() );
-        }
-        return Collections.unmodifiableMap( returnObj );
-    }
-
-    @Value
-    public static class FileUploadItem
-    {
-        private final String name;
-        private final String type;
-        private final ImmutableByteArray content;
-    }
-
     public UserIdentity getUserInfoIfLoggedIn()
     {
         final PwmSession pwmSession = getPwmSession();
@@ -335,7 +253,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
             final PwmServletDefinition pwmServletDefinition,
             final AbstractPwmServlet.ProcessAction processAction
     )
-            throws IOException, PwmUnrecoverableException
+            throws IOException
     {
         final String uri = getURLwithoutQueryString();
         if ( uri == null || uri.length() < 1 )
@@ -415,6 +333,11 @@ public class PwmRequest extends PwmHttpRequestWrapper
 
     public boolean isAuthenticated()
     {
+        if ( !hasSession() )
+        {
+            return false;
+        }
+
         return getPwmSession().isAuthenticated();
     }
 
@@ -531,7 +454,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
         }
     }
 
-    public <T extends Serializable> Optional<T> readEncryptedCookie( final String cookieName, final Class<T> returnClass )
+    public <T extends Object> Optional<T> readEncryptedCookie( final String cookieName, final Class<T> returnClass )
             throws PwmUnrecoverableException
     {
         final Optional<String> strValue = this.readCookie( cookieName );
@@ -541,9 +464,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
             return Optional.empty();
         }
 
-        final PwmSecurityKey pwmSecurityKey = getPwmSession().getSecurityKey( this );
-        final T t = getPwmDomain().getSecureService().decryptObject( strValue.get(), pwmSecurityKey, returnClass );
-        return Optional.of( t );
+        return Optional.of( decryptObject( strValue.get(), returnClass ) );
     }
 
     @Override
@@ -596,24 +517,6 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return PwmURL.appendAndEncodeUrlParameters( getURLwithoutQueryString(), readParametersAsMap() );
     }
 
-    public boolean endUserFunctionalityAvailable()
-    {
-        final PwmApplicationMode mode = pwmApplication.getApplicationMode();
-        if ( mode == PwmApplicationMode.NEW )
-        {
-            return false;
-        }
-        if ( PwmConstants.TRIAL_MODE )
-        {
-            return true;
-        }
-        if ( mode == PwmApplicationMode.RUNNING )
-        {
-            return true;
-        }
-        return false;
-    }
-
     public String getContextPath()
     {
         return this.getHttpServletRequest().getContextPath();
@@ -634,7 +537,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
 
     public PwmRequestContext getPwmRequestContext()
     {
-        return pwmRequestContext;
+        return requestContextSupplier.get();
     }
 
     private PwmRequestContext makePwmRequestContext()
@@ -742,12 +645,27 @@ public class PwmRequest extends PwmHttpRequestWrapper
     )
             throws PwmUnrecoverableException
     {
-        return getPwmSession().checkPermission( getPwmRequestContext(), permission );
+        return PwmRequestUtil.checkPermission( this, permission );
     }
 
     public ClientConnectionHolder getClientConnectionHolder()
     {
-        return getPwmSession().getClientConnectionHolder( getPwmApplication() );
+        return getPwmSession().getClientConnectionHolder( getLabel(), getPwmApplication() );
+    }
+
+    public String encryptObjectToString( final Object serializableObject )
+            throws PwmUnrecoverableException
+    {
+        final SecureService secureService = pwmApplication.domains().get( domainID ).getSecureService();
+        final PwmSecurityKey pwmSecurityKey = getPwmSession().getSecurityKey( this );
+        return secureService.encryptObjectToString( serializableObject, pwmSecurityKey );
     }
-}
 
+    public <T> T decryptObject( final String value, final Class<T> returnClass )
+            throws PwmUnrecoverableException
+    {
+        final SecureService secureService = pwmApplication.domains().get( domainID ).getSecureService();
+        final PwmSecurityKey pwmSecurityKey = getPwmSession().getSecurityKey( this );
+        return secureService.decryptObject( value, pwmSecurityKey, returnClass );
+    }
+}

+ 205 - 0
server/src/main/java/password/pwm/http/PwmRequestLocaleResolver.java

@@ -0,0 +1,205 @@
+/*
+ * 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.http;
+
+import lombok.Value;
+import password.pwm.AppProperty;
+import password.pwm.PwmConstants;
+import password.pwm.config.AppConfig;
+import password.pwm.config.DomainConfig;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+
+class PwmRequestLocaleResolver
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmRequestLocaleResolver.class );
+
+    private static final List<RequestLocaleReader> REQUEST_LOCALE_READERS = List.of(
+            new RequestParamReader(),
+            new CookieReader(),
+            new HeaderReader() );
+
+    private interface RequestLocaleReader
+    {
+        Optional<ResolvedLocale> readLocale( PwmRequest pwmRequest );
+    }
+
+    @Value
+    private static class ResolvedLocale
+    {
+        private final Locale locale;
+        private final String sourceName;
+    }
+
+    static Locale resolveRequestLocale( final PwmRequest pwmRequest )
+    {
+        final ResolvedLocale discoveredLocale = REQUEST_LOCALE_READERS.stream()
+                .map( requestLocaleReader -> requestLocaleReader.readLocale( pwmRequest ) )
+                .flatMap( Optional::stream )
+                .findFirst()
+                .orElse( new ResolvedLocale( PwmConstants.DEFAULT_LOCALE, "application default" ) );
+
+
+        return discoveredLocale.getLocale();
+    }
+
+    private static Optional<Locale> resolveInputLocaleValueStr( final AppConfig appConfig, final String incomingLocaleStr )
+    {
+        if ( "default".equalsIgnoreCase( incomingLocaleStr ) )
+        {
+            return Optional.of( PwmConstants.DEFAULT_LOCALE );
+        }
+
+        final List<Locale> knownLocales = appConfig.getKnownLocales();
+        final Locale requestedLocale = LocaleHelper.parseLocaleString( incomingLocaleStr );
+        if ( knownLocales.contains( requestedLocale ) )
+        {
+            return Optional.of( requestedLocale );
+        }
+
+        return Optional.empty();
+    }
+
+    private static String readLocaleCookieName( final PwmRequest pwmRequest )
+    {
+        return pwmRequest.getAppConfig().readAppProperty( AppProperty.HTTP_COOKIE_LOCALE_NAME );
+    }
+
+    private static class RequestParamReader implements RequestLocaleReader
+    {
+        @Override
+        public Optional<ResolvedLocale> readLocale( final PwmRequest pwmRequest )
+        {
+            final DomainConfig domainConfig = pwmRequest.getDomainConfig();
+            final String localeParamName = domainConfig.readAppProperty( AppProperty.HTTP_PARAM_NAME_LOCALE );
+
+            if ( !StringUtil.isEmpty( localeParamName ) )
+            {
+                try
+                {
+                    final String paramLocaleValueStr = pwmRequest.readParameterAsString( localeParamName );
+                    if ( !StringUtil.isEmpty( paramLocaleValueStr ) )
+                    {
+                        final Optional<Locale> requestedParamLocale = resolveInputLocaleValueStr( domainConfig.getAppConfig(), paramLocaleValueStr );
+                        if ( requestedParamLocale.isPresent() )
+                        {
+                            writeLocaleToCookie( pwmRequest, paramLocaleValueStr );
+                            return Optional.of( new ResolvedLocale( requestedParamLocale.get(), "http parameter '"
+                                    + localeParamName + "'" ) );
+                        }
+                        else
+                        {
+                            //LOGGER.debug( pwmRequest, () -> "ignoring http request parameter '" + localeParamName + "', value does not resolve to known locale" );
+                        }
+                    }
+                }
+                catch ( final PwmUnrecoverableException e )
+                {
+                    //LOGGER.trace( pwmRequest, () -> "error reading http locale request parameter: " + e.getMessage() );
+                }
+            }
+
+            return Optional.empty();
+        }
+
+        private void writeLocaleToCookie( final PwmRequest pwmRequest, final String localeStr )
+        {
+            final int cookieAgeSeconds = ( int ) pwmRequest.getAppConfig().readSettingAsLong( PwmSetting.LOCALE_COOKIE_MAX_AGE );
+            if ( cookieAgeSeconds > 0 )
+            {
+                final String localeCookieName = readLocaleCookieName( pwmRequest );
+                if ( !StringUtil.isEmpty( localeCookieName ) )
+                {
+                    try
+                    {
+                        pwmRequest.getPwmResponse().writeCookie(
+                                localeCookieName,
+                                localeStr,
+                                cookieAgeSeconds,
+                                PwmCookiePath.Domain
+                        );
+                    }
+                    catch ( final PwmUnrecoverableException e )
+                    {
+                        //LOGGER.error( pwmRequest, () -> "error writing http locale request param to cookie: " + e.getMessage() );
+                    }
+                }
+            }
+        }
+    }
+
+    private static class CookieReader implements RequestLocaleReader
+    {
+        @Override
+        public Optional<ResolvedLocale> readLocale( final PwmRequest pwmRequest )
+        {
+            final String localeCookieName = readLocaleCookieName( pwmRequest );
+            final Optional<String> localeCookie = pwmRequest.readCookie( localeCookieName );
+            if ( localeCookie.isPresent() )
+            {
+                final Optional<Locale> requestedCookieLocale = resolveInputLocaleValueStr( pwmRequest.getAppConfig(), localeCookie.get() );
+                if ( requestedCookieLocale.isPresent() )
+                {
+                    return Optional.of( new ResolvedLocale( requestedCookieLocale.get(),
+                            "http " + HttpHeader.SetCookie + " '" + localeCookieName + "' header" ) );
+                }
+                else
+                {
+                    //LOGGER.debug( pwmRequest, () -> "ignoring cookie locale value, value does not resolve to known locale" );
+                }
+            }
+
+            return Optional.empty();
+        }
+    }
+
+    private static class HeaderReader implements RequestLocaleReader
+    {
+        @Override
+        public Optional<ResolvedLocale> readLocale( final PwmRequest pwmRequest )
+        {
+            final Enumeration<Locale> requestLocales = pwmRequest.getHttpServletRequest().getLocales();
+            while ( requestLocales.hasMoreElements() )
+            {
+                final Locale nextRequestLocale = requestLocales.nextElement();
+                final Optional<Locale> resolvedLocale = resolveInputLocaleValueStr( pwmRequest.getAppConfig(), nextRequestLocale.toString() );
+                if ( resolvedLocale.isPresent() )
+                {
+                    return Optional.of( new ResolvedLocale( resolvedLocale.get(), "http " + HttpHeader.AcceptLanguage + " header" ) );
+                }
+                else
+                {
+                    //LOGGER.debug( pwmRequest, () -> "ignoring unknown locale value set in http request for locale '" + nextRequestLocale + "'" );
+                }
+            }
+
+            return Optional.empty();
+        }
+    }
+}

+ 260 - 0
server/src/main/java/password/pwm/http/PwmRequestUtil.java

@@ -0,0 +1,260 @@
+/*
+ * 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.http;
+
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.validator.routines.InetAddressValidator;
+import password.pwm.Permission;
+import password.pwm.PwmDomain;
+import password.pwm.bean.LocalSessionStateBean;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.AppConfig;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.value.data.UserPermission;
+import password.pwm.data.FileUploadItem;
+import password.pwm.data.ImmutableByteArray;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.permission.UserPermissionUtility;
+import password.pwm.user.UserInfo;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public class PwmRequestUtil
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmRequestUtil.class );
+
+    public static Map<String, FileUploadItem> readFileUploads(
+            final PwmRequest pwmRequest,
+            final int maxFileSize,
+            final int maxItems
+    )
+            throws PwmUnrecoverableException
+    {
+        final Map<String, FileUploadItem> returnObj = new LinkedHashMap<>();
+        try
+        {
+            if ( ServletFileUpload.isMultipartContent( pwmRequest.getHttpServletRequest() ) )
+            {
+                final ServletFileUpload upload = new ServletFileUpload();
+                final FileItemIterator iter = upload.getItemIterator( pwmRequest.getHttpServletRequest() );
+                while ( iter.hasNext() && returnObj.size() < maxItems )
+                {
+                    final FileItemStream item = iter.next();
+                    final InputStream inputStream = item.openStream();
+                    final ImmutableByteArray fileContents = JavaHelper.copyToBytes( inputStream, maxFileSize + 1 );
+                    if ( fileContents.size() > maxFileSize )
+                    {
+                        final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "upload file size limit exceeded" );
+                        LOGGER.error( pwmRequest, errorInformation );
+                        pwmRequest.respondWithError( errorInformation );
+                        return Collections.emptyMap();
+                    }
+                    final FileUploadItem fileUploadItem = new FileUploadItem(
+                            item.getName(),
+                            item.getContentType(),
+                            fileContents
+                    );
+                    returnObj.put( item.getFieldName(), fileUploadItem );
+                }
+            }
+        }
+        catch ( final Exception e )
+        {
+            LOGGER.error( () -> "error reading file upload: " + e.getMessage() );
+        }
+        return Collections.unmodifiableMap( returnObj );
+    }
+
+    static SessionLabel makeSessionLabel( final PwmRequest pwmRequest )
+    {
+        final SessionLabel.SessionLabelBuilder builder = SessionLabel.builder();
+
+        if ( pwmRequest.hasSession() )
+        {
+            final PwmSession pwmSession = pwmRequest.getPwmSession();
+            final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
+
+            if ( pwmSession.isAuthenticated() )
+            {
+                try
+                {
+                    final UserInfo userInfo = pwmSession.getUserInfo();
+                    final UserIdentity userIdentity = userInfo.getUserIdentity();
+
+                    builder.username( userInfo.getUsername() );
+                    builder.profile( userIdentity == null ? null : userIdentity.getLdapProfileID() );
+                }
+                catch ( final PwmUnrecoverableException e )
+                {
+                    LOGGER.error( () -> "unexpected error reading username: " + e.getMessage(), e );
+                }
+            }
+
+            builder.sessionID( ssBean.getSessionID() );
+        }
+
+        builder.sourceAddress( pwmRequest.getSrcAddress().orElse( null ) );
+        builder.sourceHostname( pwmRequest.getSrcHostname().orElse( null ) );
+        builder.requestID( pwmRequest.getPwmRequestID() );
+        builder.domain( pwmRequest.getDomainID().stringValue() );
+
+        return builder.build();
+    }
+
+    public static Optional<String> readUserHostname(
+            final HttpServletRequest request,
+            final AppConfig config
+    )
+    {
+        if ( config != null && !config.readSettingAsBoolean( PwmSetting.REVERSE_DNS_ENABLE ) )
+        {
+            return Optional.empty();
+        }
+
+        final Optional<String> userIPAddress = readUserNetworkAddress( request, config );
+        if ( userIPAddress.isPresent() )
+        {
+            try
+            {
+                return Optional.of( InetAddress.getByName( userIPAddress.get() ).getCanonicalHostName() );
+            }
+            catch ( final UnknownHostException e )
+            {
+                LOGGER.trace( () -> "unknown host while trying to compute hostname for src request: " + e.getMessage() );
+            }
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Returns the IP address of the user.  If there is an X-Forwarded-For header in the request, that address will
+     * be used.  Otherwise, the source address of the request is used.
+     *
+     * @param request the http request object
+     * @param config the application configuration
+     * @return String containing the textual representation of the source IP address, or null if the request is invalid.
+     */
+    public static Optional<String> readUserNetworkAddress(
+            final HttpServletRequest request,
+            final AppConfig config
+    )
+    {
+        final List<String> candidateAddresses = new ArrayList<>();
+
+        final boolean useXForwardedFor = config != null && config.readSettingAsBoolean( PwmSetting.USE_X_FORWARDED_FOR_HEADER );
+        if ( useXForwardedFor )
+        {
+            final String xForwardedForValue = request.getHeader( HttpHeader.XForwardedFor.getHttpName() );
+            if ( StringUtil.notEmpty( xForwardedForValue ) )
+            {
+                Collections.addAll( candidateAddresses, xForwardedForValue.split( "," ) );
+            }
+        }
+
+        final String sourceIP = request.getRemoteAddr();
+        if ( StringUtil.notEmpty( sourceIP ) )
+        {
+            candidateAddresses.add( sourceIP );
+        }
+
+        for ( final String candidateAddress : candidateAddresses )
+        {
+            final String trimAddr = candidateAddress.trim();
+            if ( InetAddressValidator.getInstance().isValid( trimAddr ) )
+            {
+                return Optional.of( trimAddr );
+            }
+            else
+            {
+                LOGGER.warn( () -> "discarding bogus source network address '" + trimAddr + "'" );
+            }
+        }
+
+        return Optional.empty();
+    }
+
+    public static boolean checkPermission(
+            final PwmRequest pwmRequest,
+            final Permission permission
+    )
+            throws PwmUnrecoverableException
+    {
+        final Instant startTime = Instant.now();
+
+        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
+
+        if ( !pwmRequest.isAuthenticated() )
+        {
+            LOGGER.trace( pwmRequest, () -> "user is not authenticated, returning false for permission check" );
+            return false;
+        }
+
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        Permission.PermissionStatus status = pwmRequest.getPwmSession().getUserSessionDataCacheBean().getPermission( permission );
+        if ( status == Permission.PermissionStatus.UNCHECKED )
+        {
+            LOGGER.debug( pwmRequest, () -> "checking permission " + permission.toString() + " for user "
+                    + pwmSession.getUserInfo().getUserIdentity().toDisplayString() );
+
+            if ( permission == Permission.PWMADMIN && !pwmDomain.getConfig().isAdministrativeDomain() )
+            {
+                status = Permission.PermissionStatus.DENIED;
+            }
+            else
+            {
+                final PwmSetting setting = permission.getPwmSetting();
+                final List<UserPermission> userPermission = pwmDomain.getConfig().readSettingAsUserPermission( setting );
+                final boolean result = UserPermissionUtility.testUserPermission( pwmDomain, pwmRequest.getLabel(), pwmSession.getUserInfo().getUserIdentity(), userPermission );
+                status = result ? Permission.PermissionStatus.GRANTED : Permission.PermissionStatus.DENIED;
+            }
+
+            pwmSession.getUserSessionDataCacheBean().setPermission( permission, status );
+
+            {
+                final String debugName = pwmSession.getUserInfo().getUserIdentity().toDisplayString();
+                final Permission.PermissionStatus finalStatus = status;
+                LOGGER.debug( pwmRequest.getLabel(), () -> "permission " + permission + " for user " + debugName + " is " + finalStatus, TimeDuration.fromCurrent( startTime ) );
+            }
+        }
+
+        return status == Permission.PermissionStatus.GRANTED;
+    }
+
+}

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

@@ -34,10 +34,8 @@ import password.pwm.http.servlet.command.CommandServlet;
 import password.pwm.i18n.Message;
 import password.pwm.util.Validator;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.json.JsonFactory;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.ws.server.RestResultBean;
 
 import javax.servlet.ServletContext;
@@ -251,9 +249,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
     public void writeEncryptedCookie( final String cookieName, final Serializable cookieValue, final int seconds, final PwmCookiePath path )
             throws PwmUnrecoverableException
     {
-        final String jsonValue = JsonFactory.get().serialize( cookieValue );
-        final PwmSecurityKey pwmSecurityKey = pwmRequest.getPwmSession().getSecurityKey( pwmRequest );
-        final String encryptedValue = pwmRequest.getPwmDomain().getSecureService().encryptToString( jsonValue, pwmSecurityKey );
+        final String encryptedValue = pwmRequest.encryptObjectToString( cookieValue );
         writeCookie( cookieName, encryptedValue, seconds, path, PwmHttpResponseWrapper.Flag.BypassSanitation );
     }
 

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

@@ -23,7 +23,6 @@ package password.pwm.http;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import lombok.EqualsAndHashCode;
 import password.pwm.AppProperty;
-import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
@@ -32,9 +31,7 @@ import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.ChallengeProfile;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -42,12 +39,10 @@ import password.pwm.http.bean.UserSessionDataCacheBean;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.auth.AuthenticationResult;
 import password.pwm.ldap.auth.AuthenticationType;
-import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.user.UserInfo;
 import password.pwm.user.UserInfoBean;
-import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.MiscUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.json.JsonFactory;
@@ -60,8 +55,6 @@ import java.io.Serializable;
 import java.time.Instant;
 import java.util.Date;
 import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.locks.Lock;
@@ -87,27 +80,18 @@ public class PwmSession implements Serializable
     private LoginInfoBean loginInfoBean;
     private transient UserInfo userInfo;
 
-    private static final Lock CREATION_LOCK = new ReentrantLock();
-
     private final Lock securityKeyLock = new ReentrantLock();
     private transient ClientConnectionHolder clientConnectionHolder;
 
     public static PwmSession createPwmSession( final PwmDomain pwmDomain )
     {
-        CREATION_LOCK.lock();
-        try
-        {
-            return new PwmSession( pwmDomain );
-        }
-        finally
-        {
-            CREATION_LOCK.unlock();
-        }
+         return new PwmSession( pwmDomain );
     }
 
     private PwmSession( final PwmDomain pwmDomain )
     {
         Objects.requireNonNull( pwmDomain );
+
         this.domainID = pwmDomain.getDomainID();
 
         this.sessionStateBean.setSessionID( pwmDomain.getSessionTrackService().generateNewSessionID() );
@@ -122,7 +106,7 @@ public class PwmSession implements Serializable
     }
 
 
-    public ClientConnectionHolder getClientConnectionHolder( final PwmApplication pwmApplication )
+    public ClientConnectionHolder getClientConnectionHolder( final SessionLabel sessionLabel, final PwmApplication pwmApplication )
     {
         if ( clientConnectionHolder != null )
         {
@@ -134,7 +118,7 @@ public class PwmSession implements Serializable
 
         if ( clientConnectionHolder == null )
         {
-            clientConnectionHolder = ClientConnectionHolder.unauthenticatedClientConnectionContext( pwmApplication, domainID, this::getLabel );
+            clientConnectionHolder = ClientConnectionHolder.unauthenticatedClientConnectionContext( pwmApplication, domainID, () -> sessionLabel );
         }
 
         return clientConnectionHolder;
@@ -219,40 +203,6 @@ public class PwmSession implements Serializable
         return userSessionDataCacheBean;
     }
 
-    public SessionLabel getLabel( )
-    {
-        final LocalSessionStateBean ssBean = this.getSessionStateBean();
-
-        UserIdentity userIdentity = null;
-        String userID = null;
-        String profile = null;
-
-        if ( isAuthenticated() )
-        {
-            try
-            {
-                final UserInfo userInfo = getUserInfo();
-                userIdentity = userInfo.getUserIdentity();
-                userID = userInfo.getUsername();
-                profile = userIdentity == null ? null : userIdentity.getLdapProfileID();
-            }
-            catch ( final PwmUnrecoverableException e )
-            {
-                LOGGER.error( () -> "unexpected error reading username: " + e.getMessage(), e );
-            }
-        }
-
-        return SessionLabel.builder()
-                .sessionID( ssBean.getSessionID() )
-                .userID( userIdentity == null ? null : userIdentity.toDelimitedKey() )
-                .username( userID )
-                .domain( domainID.stringValue() )
-                .profile( profile )
-                .sourceAddress( ssBean.getSrcAddress() )
-                .sourceHostname( ssBean.getSrcHostname() )
-                .build();
-    }
-
     /**
      * UnAuthenticate the pwmSession.
      *
@@ -282,7 +232,7 @@ public class PwmSession implements Serializable
             // close out any outstanding connections
             if ( pwmRequest != null && clientConnectionHolder != null )
             {
-                getClientConnectionHolder( pwmRequest.getPwmApplication() ).closeConnections();
+                getClientConnectionHolder( pwmRequest.getLabel(), pwmRequest.getPwmApplication() ).closeConnections();
             }
             clientConnectionHolder = null;
 
@@ -351,38 +301,6 @@ public class PwmSession implements Serializable
         return "PwmSession instance: " + JsonFactory.get().serializeMap( debugData );
     }
 
-    public boolean setLocale( final PwmRequest pwmRequest, final String localeString )
-            throws PwmUnrecoverableException
-    {
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        if ( pwmDomain == null )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_APP_UNAVAILABLE, "unable to read context manager" ) );
-        }
-
-        final LocalSessionStateBean ssBean = this.getSessionStateBean();
-        final List<Locale> knownLocales = pwmRequest.getAppConfig().getKnownLocales();
-        final Locale requestedLocale = LocaleHelper.parseLocaleString( localeString );
-        if ( knownLocales.contains( requestedLocale ) || "default".equalsIgnoreCase( localeString ) )
-        {
-            LOGGER.debug( pwmRequest, () -> "setting session locale to '" + localeString + "'" );
-            ssBean.setLocale( "default".equalsIgnoreCase( localeString )
-                    ? PwmConstants.DEFAULT_LOCALE
-                    : requestedLocale );
-            if ( this.isAuthenticated() )
-            {
-                this.reloadUserInfoBean( pwmRequest );
-            }
-            return true;
-        }
-        else
-        {
-            LOGGER.error( pwmRequest, () -> "ignoring unknown locale value set request for locale '" + localeString + "'" );
-            ssBean.setLocale( PwmConstants.DEFAULT_LOCALE );
-            return false;
-        }
-    }
-
     public boolean isAuthenticated( )
     {
         return getLoginInfoBean().isAuthenticated();
@@ -441,6 +359,7 @@ public class PwmSession implements Serializable
     }
 
     public void updateLdapAuthentication(
+            final SessionLabel sessionLabel,
             final PwmApplication pwmApplication,
             final UserIdentity userIdentity,
             final AuthenticationResult authenticationResult
@@ -449,59 +368,11 @@ public class PwmSession implements Serializable
         clientConnectionHolder = ClientConnectionHolder.authenticatedClientConnectionContext(
                 pwmApplication,
                 userIdentity,
-                this::getLabel,
+                () -> sessionLabel,
                 authenticationResult.getUserProvider(),
                 authenticationResult.getAuthenticationType() );
     }
 
-    public boolean checkPermission(
-            final PwmRequestContext pwmRequestContext,
-            final Permission permission
-    )
-            throws PwmUnrecoverableException
-    {
-        final Instant startTime = Instant.now();
-
-        final SessionLabel sessionLabel = pwmRequestContext.getSessionLabel();
-        final PwmDomain pwmDomain = pwmRequestContext.getPwmDomain();
-
-        if ( !isAuthenticated() )
-        {
-            LOGGER.trace( sessionLabel, () -> "user is not authenticated, returning false for permission check" );
-            return false;
-        }
-
-        Permission.PermissionStatus status = getUserSessionDataCacheBean().getPermission( permission );
-        if ( status == Permission.PermissionStatus.UNCHECKED )
-        {
-            LOGGER.debug( sessionLabel, () -> "checking permission " + permission.toString() + " for user " + getUserInfo().getUserIdentity().toDisplayString() );
-
-            if ( permission == Permission.PWMADMIN && !pwmDomain.getConfig().isAdministrativeDomain() )
-            {
-                status = Permission.PermissionStatus.DENIED;
-            }
-            else
-            {
-                final PwmSetting setting = permission.getPwmSetting();
-                final List<UserPermission> userPermission = pwmDomain.getConfig().readSettingAsUserPermission( setting );
-                final boolean result = UserPermissionUtility.testUserPermission( pwmDomain, sessionLabel, getUserInfo().getUserIdentity(), userPermission );
-                status = result ? Permission.PermissionStatus.GRANTED : Permission.PermissionStatus.DENIED;
-            }
-
-            getUserSessionDataCacheBean().setPermission( permission, status );
-
-            {
-                final String debugName = isAuthenticated()
-                        ? getUserInfo().getUserIdentity().toDelimitedKey()
-                        : "[unauthenticated]";
-                final Permission.PermissionStatus finalStatus = status;
-                LOGGER.debug( sessionLabel, () -> "permission " + permission + " for user " + debugName + " is " + finalStatus, TimeDuration.fromCurrent( startTime ) );
-            }
-        }
-
-        return status == Permission.PermissionStatus.GRANTED;
-    }
-
     public MacroRequest getMacroMachine(
             final PwmRequestContext pwmRequestContext
     )

+ 11 - 10
server/src/main/java/password/pwm/http/PwmSessionFactory.java

@@ -39,6 +39,14 @@ public class PwmSessionFactory
     {
     }
 
+    private static PwmSession createSession( final PwmRequest pwmRequest, final PwmDomain pwmDomain )
+    {
+        final PwmSession pwmSession = PwmSession.createPwmSession( pwmDomain );
+
+        pwmSession.getSessionStateBean().setLocale( pwmRequest.getLocale() );
+
+        return pwmSession;
+    }
 
     public static void sessionMerge(
             final PwmRequest pwmRequest,
@@ -53,18 +61,11 @@ public class PwmSessionFactory
         setHttpSessionIdleTimeout( pwmDomain, pwmRequest, httpSession );
     }
 
-    private static PwmSession createSession( final PwmDomain pwmDomain )
-    {
-        // handle pwmSession init and assignment.
-
-        return PwmSession.createPwmSession( pwmDomain );
-    }
-
-
-    public static PwmSession readPwmSession( final HttpSession httpSession, final PwmDomain pwmdomain )
+    public static PwmSession readPwmSession( final PwmRequest pwmRequest, final PwmDomain pwmdomain )
     {
+        final HttpSession httpSession = pwmRequest.getHttpServletRequest().getSession();
         final Map<DomainID, PwmSession> map = getDomainSessionMap( httpSession );
-        return map.computeIfAbsent( pwmdomain.getDomainID(), k -> createSession( pwmdomain ) );
+        return map.computeIfAbsent( pwmdomain.getDomainID(), k -> createSession( pwmRequest, pwmdomain ) );
     }
 
     public static Map<DomainID, PwmSession> getDomainSessionMap( final HttpSession httpSession )

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

@@ -30,7 +30,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.BasicAuthInfo;
@@ -76,8 +76,8 @@ public class BasicFilterAuthenticationProvider implements PwmHttpFilterAuthentic
                     pwmRequest,
                     PwmAuthenticationSource.BASIC_AUTH
             );
-            final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
-            final UserIdentity userIdentity = userSearchEngine.resolveUsername( basicAuthInfo.get().getUsername(), null, null, pwmRequest.getLabel() );
+            final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
+            final UserIdentity userIdentity = userSearchService.resolveUsername( basicAuthInfo.get().getUsername(), null, null, pwmRequest.getLabel() );
             sessionAuthenticator.authenticateUser( userIdentity, basicAuthInfo.get().getPassword() );
             pwmSession.getLoginInfoBean().setBasicAuth( basicAuthInfo.get() );
         }

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

@@ -122,7 +122,7 @@ public class ApplicationModeFilter extends AbstractPwmFilter
 
         if ( mode == PwmApplicationMode.ERROR )
         {
-            ErrorInformation rootError = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getStartupErrorInformation();
+            ErrorInformation rootError = ContextManager.getContextManager( pwmRequest ).getStartupErrorInformation();
             if ( rootError == null )
             {
                 rootError = new ErrorInformation( PwmError.ERROR_APP_UNAVAILABLE, "Application startup failed." );

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

@@ -99,7 +99,7 @@ public class ConfigAccessFilter extends AbstractPwmFilter
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest ).getConfigReader();
         final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration();
 
         checkPreconditions( pwmRequest, storedConfig );

+ 14 - 112
server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -20,7 +20,6 @@
 
 package password.pwm.http.filter;
 
-import org.apache.commons.validator.routines.InetAddressValidator;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
@@ -39,6 +38,7 @@ import password.pwm.http.JspUrl;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
+import password.pwm.http.PwmRequestUtil;
 import password.pwm.http.PwmResponse;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSessionFactory;
@@ -48,7 +48,6 @@ import password.pwm.svc.intruder.IntruderServiceClient;
 import password.pwm.svc.stats.EpsStatistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsService;
-import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -66,14 +65,9 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import java.io.IOException;
 import java.math.BigInteger;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.time.Instant;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -306,7 +300,7 @@ public class RequestInitializationFilter implements Filter
     private void checkAndInitSessionState( final HttpServletRequest request )
             throws PwmUnrecoverableException
     {
-        final ContextManager contextManager = ContextManager.getContextManager( request.getSession() );
+        final ContextManager contextManager = ContextManager.getContextManager( request.getServletContext() );
         final PwmApplication pwmApplication = contextManager.getPwmApplication();
 
         // destroy any outdated sessions
@@ -470,75 +464,6 @@ public class RequestInitializationFilter implements Filter
     }
 
 
-    public static Optional<String> readUserHostname( final HttpServletRequest request, final AppConfig config ) throws PwmUnrecoverableException
-    {
-        if ( config != null && !config.readSettingAsBoolean( PwmSetting.REVERSE_DNS_ENABLE ) )
-        {
-            return Optional.empty();
-        }
-
-        final Optional<String> userIPAddress = readUserNetworkAddress( request, config );
-        if ( userIPAddress.isPresent() )
-        {
-            try
-            {
-                return Optional.of( InetAddress.getByName( userIPAddress.get() ).getCanonicalHostName() );
-            }
-            catch ( final UnknownHostException e )
-            {
-                LOGGER.trace( () -> "unknown host while trying to compute hostname for src request: " + e.getMessage() );
-            }
-        }
-        return Optional.empty();
-    }
-
-    /**
-     * Returns the IP address of the user.  If there is an X-Forwarded-For header in the request, that address will
-     * be used.  Otherwise, the source address of the request is used.
-     *
-     * @param request the http request object
-     * @param config the application configuration
-     * @return String containing the textual representation of the source IP address, or null if the request is invalid.
-     */
-    public static Optional<String> readUserNetworkAddress(
-            final HttpServletRequest request,
-            final AppConfig config
-    )
-    {
-        final List<String> candidateAddresses = new ArrayList<>();
-
-        final boolean useXForwardedFor = config != null && config.readSettingAsBoolean( PwmSetting.USE_X_FORWARDED_FOR_HEADER );
-        if ( useXForwardedFor )
-        {
-            final String xForwardedForValue = request.getHeader( HttpHeader.XForwardedFor.getHttpName() );
-            if ( StringUtil.notEmpty( xForwardedForValue ) )
-            {
-                Collections.addAll( candidateAddresses, xForwardedForValue.split( "," ) );
-            }
-        }
-
-        final String sourceIP = request.getRemoteAddr();
-        if ( StringUtil.notEmpty( sourceIP ) )
-        {
-            candidateAddresses.add( sourceIP );
-        }
-
-        for ( final String candidateAddress : candidateAddresses )
-        {
-            final String trimAddr = candidateAddress.trim();
-            if ( InetAddressValidator.getInstance().isValid( trimAddr ) )
-            {
-                return Optional.of( trimAddr );
-            }
-            else
-            {
-                LOGGER.warn( () -> "discarding bogus source network address '" + trimAddr + "'" );
-            }
-        }
-
-        return Optional.empty();
-    }
-
     private static void handleRequestInitialization(
             final PwmRequest pwmRequest
     )
@@ -557,13 +482,13 @@ public class RequestInitializationFilter implements Filter
         // mark session ip address
         if ( ssBean.getSrcAddress() == null )
         {
-            ssBean.setSrcAddress( readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getAppConfig() ).orElse( "" ) );
+            ssBean.setSrcAddress( pwmRequest.getSrcAddress().orElse( "" ) );
         }
 
         // mark the user's hostname in the session bean
         if ( ssBean.getSrcHostname() == null )
         {
-            ssBean.setSrcHostname( readUserHostname( pwmRequest.getHttpServletRequest(), pwmRequest.getAppConfig() ).orElse( "" ) );
+            ssBean.setSrcHostname( pwmRequest.getSrcHostname().orElse( "" ) );
         }
 
         // update the privateUrlAccessed flag
@@ -572,50 +497,27 @@ public class RequestInitializationFilter implements Filter
             ssBean.setPrivateUrlAccessed( true );
         }
 
-        // initialize the session's locale
-        if ( ssBean.getLocale() == null )
-        {
-            initializeLocaleAndTheme( pwmRequest );
-        }
+        initializeTheme( pwmRequest );
 
         // set idle timeout
         PwmSessionFactory.setHttpSessionIdleTimeout( pwmRequest.getPwmDomain(), pwmRequest, pwmRequest.getHttpServletRequest().getSession() );
     }
 
-    private static void initializeLocaleAndTheme(
+    private static void initializeTheme(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException
     {
+        final String themeCookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_THEME_NAME );
+        if ( StringUtil.notEmpty( themeCookieName ) )
         {
-            final String localeCookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_LOCALE_NAME );
-            final Optional<String> localeCookie = pwmRequest.readCookie( localeCookieName );
-            if ( localeCookie.isPresent() )
-            {
-                LOGGER.debug( pwmRequest, () -> "detected locale cookie in request, setting locale to " + localeCookie );
-                pwmRequest.getPwmSession().setLocale( pwmRequest, localeCookie.get() );
-            }
-            else
-            {
-                final List<Locale> knownLocales = pwmRequest.getAppConfig().getKnownLocales();
-                final Locale userLocale = LocaleHelper.localeResolver( pwmRequest.getHttpServletRequest().getLocale(), knownLocales );
-                pwmRequest.getPwmSession().getSessionStateBean().setLocale( userLocale == null ? PwmConstants.DEFAULT_LOCALE : userLocale );
-                LOGGER.trace( pwmRequest, () -> "user locale set to '" + pwmRequest.getLocale() + "'" );
-            }
-        }
-
-        {
-            final String themeCookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_THEME_NAME );
-            if ( StringUtil.notEmpty( themeCookieName ) )
+            final Optional<String> themeCookie = pwmRequest.readCookie( themeCookieName );
+            if ( themeCookie.isPresent() )
             {
-                final Optional<String> themeCookie = pwmRequest.readCookie( themeCookieName );
-                if ( themeCookie.isPresent() )
+                if ( pwmRequest.getPwmDomain().getResourceServletService().checkIfThemeExists( pwmRequest, themeCookie.get() ) )
                 {
-                    if ( pwmRequest.getPwmDomain().getResourceServletService().checkIfThemeExists( pwmRequest, themeCookie.get() ) )
-                    {
-                        LOGGER.debug( pwmRequest, () -> "detected theme cookie in request, setting theme to " + themeCookie );
-                        pwmRequest.getPwmSession().getSessionStateBean().setTheme( themeCookie.get() );
-                    }
+                    LOGGER.debug( pwmRequest, () -> "detected theme cookie in request, setting theme to " + themeCookie );
+                    pwmRequest.getPwmSession().getSessionStateBean().setTheme( themeCookie.get() );
                 }
             }
         }
@@ -648,7 +550,7 @@ public class RequestInitializationFilter implements Filter
     {
         if ( !pwmRequest.getAppConfig().readSettingAsBoolean( PwmSetting.MULTI_IP_SESSION_ALLOWED ) )
         {
-            final Optional<String> optionalRemoteAddress = readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getAppConfig() );
+            final Optional<String> optionalRemoteAddress = PwmRequestUtil.readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getAppConfig() );
             final LocalSessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
 
             if ( optionalRemoteAddress.isPresent() )

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

@@ -58,6 +58,8 @@ import java.net.URI;
 import java.net.UnknownHostException;
 import java.time.Instant;
 import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
 
 /**
  * <p>This session filter (invoked by the container through the web.xml descriptor) wraps all calls to the
@@ -74,7 +76,6 @@ public class SessionFilter extends AbstractPwmFilter
 
     private static final List<CheckingFunction> CHECKING_FUNCTIONS = List.of(
             new SessionVerificationChecker(),
-            new LocaleParamChecker(),
             new ThemeParamChecker(),
             new SsoOverrideParamChecker(),
             new ForwardParamChecker(),
@@ -139,8 +140,22 @@ public class SessionFilter extends AbstractPwmFilter
         pwmRequest.getPwmDomain().getStatisticsManager().updateAverageValue( AvgStatistic.AVG_REQUEST_PROCESS_TIME, requestExecuteTime.asMillis() );
         pwmRequest.getPwmSession().getSessionStateBean().getRequestCount().incrementAndGet();
         pwmRequest.getPwmSession().getSessionStateBean().getAvgRequestDuration().update( requestExecuteTime.asDuration() );
+        updateSessionLocale( pwmRequest );
     }
 
+    private static void updateSessionLocale( final PwmRequest pwmRequest )
+    {
+        final Locale locale = pwmRequest.getLocale();
+        final LocalSessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
+
+        if ( !Objects.equals( ssBean.getLocale(), locale ) )
+        {
+            LOGGER.debug( pwmRequest, () -> "setting session locale to '" + locale + "'" );
+            ssBean.setLocale( locale );
+        }
+    }
+
+
     private ProcessStatus handleStandardRequestOperations(
             final PwmRequest pwmRequest
     )
@@ -366,35 +381,6 @@ public class SessionFilter extends AbstractPwmFilter
         }
     }
 
-    //override session locale due to parameter
-    private static class LocaleParamChecker implements CheckingFunction
-    {
-        @Override
-        public ProcessStatus processCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
-        {
-            final DomainConfig config = pwmRequest.getDomainConfig();
-            final String localeParamName = config.readAppProperty( AppProperty.HTTP_PARAM_NAME_LOCALE );
-            final String localeCookieName = config.readAppProperty( AppProperty.HTTP_COOKIE_LOCALE_NAME );
-            final String requestedLocale = pwmRequest.readParameterAsString( localeParamName );
-            final int cookieAgeSeconds = ( int ) pwmRequest.getAppConfig().readSettingAsLong( PwmSetting.LOCALE_COOKIE_MAX_AGE );
-            if ( requestedLocale != null && requestedLocale.length() > 0 )
-            {
-                LOGGER.debug( pwmRequest, () -> "detected locale request parameter " + localeParamName + " with value " + requestedLocale );
-                if ( cookieAgeSeconds > 0
-                        && pwmRequest.getPwmSession().setLocale( pwmRequest, requestedLocale ) )
-                {
-                    pwmRequest.getPwmResponse().writeCookie(
-                            localeCookieName,
-                            requestedLocale,
-                            cookieAgeSeconds,
-                            PwmCookiePath.Domain
-                    );
-                }
-            }
-            return ProcessStatus.Continue;
-        }
-    }
-
     //set the session's theme
     private static class ThemeParamChecker implements CheckingFunction
     {

+ 15 - 25
server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java

@@ -49,6 +49,7 @@ import password.pwm.svc.sessiontrack.UserAgentUtils;
 import password.pwm.svc.stats.EpsStatistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsService;
+import password.pwm.user.UserInfo;
 import password.pwm.util.i18n.LocaleComparators;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
@@ -65,7 +66,6 @@ import password.pwm.ws.server.rest.bean.PublicHealthData;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.io.Serializable;
 import java.time.Instant;
@@ -153,17 +153,17 @@ public class ClientApiServlet extends ControlledPwmServlet
 
     @ActionHandler( action = "clientData" )
     public ProcessStatus processRestClientData( final PwmRequest pwmRequest )
-            throws PwmUnrecoverableException, IOException, ChaiUnavailableException
+            throws PwmUnrecoverableException, IOException
     {
         final String pageUrl = pwmRequest.readParameterAsString( "pageUrl", PwmHttpRequestWrapper.Flag.BypassValidation );
         final String etagParam = pwmRequest.readParameterAsString( "etag", PwmHttpRequestWrapper.Flag.BypassValidation );
 
         final int maxCacheAgeSeconds = 60 * 5;
 
-        final String eTagValue = makeClientEtag( pwmRequest.getPwmDomain(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest() );
+        final String eTagValue = makeClientEtag( pwmRequest );
 
         // check the incoming header;
-        final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString( "If-None-Match" );
+        final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString( HttpHeader.If_None_Match );
 
         if ( ifNoneMatchValue != null && ifNoneMatchValue.equals( eTagValue ) && eTagValue.equals( etagParam ) )
         {
@@ -193,7 +193,7 @@ public class ClientApiServlet extends ControlledPwmServlet
         final String bundleName = pwmRequest.readParameterAsString( "bundle" );
         final int maxCacheAgeSeconds = 60 * 5;
 
-        final String eTagValue = makeClientEtag( pwmRequest.getPwmDomain(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest() );
+        final String eTagValue = makeClientEtag( pwmRequest );
 
         pwmRequest.getPwmResponse().setHeader( HttpHeader.ETag, eTagValue );
         pwmRequest.getPwmResponse().setHeader( HttpHeader.Expires, String.valueOf( System.currentTimeMillis() + ( maxCacheAgeSeconds * 1000 ) ) );
@@ -254,32 +254,22 @@ public class ClientApiServlet extends ControlledPwmServlet
     public static String makeClientEtag( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        return makeClientEtag( pwmRequest.getPwmDomain(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest() );
-    }
+        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
 
-    public static String makeClientEtag(
-            final PwmDomain pwmDomain,
-            final PwmSession pwmSession,
-            final HttpServletRequest httpServletRequest
-    )
-            throws PwmUnrecoverableException
-    {
         final StringBuilder inputString = new StringBuilder();
         inputString.append( PwmConstants.BUILD_NUMBER );
         inputString.append( pwmDomain.getPwmApplication().getStartupTime().toEpochMilli() );
-        inputString.append( httpServletRequest.getSession().getMaxInactiveInterval() );
+        inputString.append( pwmDomain.getDomainID().toString() );
         inputString.append( pwmDomain.getPwmApplication().getRuntimeNonce() );
+        inputString.append( pwmRequest.getHttpServletRequest().getSession().getMaxInactiveInterval() );
 
-        if ( pwmSession.getSessionStateBean().getLocale() != null )
-        {
-            inputString.append( pwmSession.getSessionStateBean().getLocale() );
-        }
+        inputString.append( pwmRequest.getLocale().toString() );
 
-        inputString.append( pwmSession.getSessionStateBean().getSessionID() );
-        if ( pwmSession.isAuthenticated() )
+        if ( pwmRequest.isAuthenticated() )
         {
-            inputString.append( pwmSession.getUserInfo().getUserGuid() );
-            inputString.append( pwmSession.getLoginInfoBean().getAuthTime() );
+            final UserInfo userInfo = pwmRequest.getPwmSession().getUserInfo();
+            inputString.append( userInfo.getUserGuid() );
+            inputString.append( userInfo.getUserIdentity().toDisplayString() );
         }
 
         return SecureEngine.hash( inputString.toString(), PwmHashAlgorithm.SHA1 ).toLowerCase();
@@ -290,7 +280,7 @@ public class ClientApiServlet extends ControlledPwmServlet
             final PwmRequest pwmRequest,
             final String pageUrl
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
         final AppData appData = new AppData();
         appData.PWM_GLOBAL = makeClientData( pwmDomain, pwmRequest, pageUrl );
@@ -305,7 +295,7 @@ public class ClientApiServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final Locale userLocale = pwmSession.getSessionStateBean().getLocale();
+        final Locale userLocale = pwmRequest.getLocale();
 
         final DomainConfig config = pwmDomain.getConfig();
         final TreeMap<String, Object> settingMap = new TreeMap<>();

+ 5 - 5
server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -42,7 +42,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.intruder.IntruderServiceClient;
 import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.Statistic;
@@ -181,19 +181,19 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
 
             final UserIdentity userIdentity;
             {
-                final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+                final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
                 final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                         .filter( searchFilter )
                         .formValues( formValues )
                         .ldapProfile( ldapProfile )
                         .contexts( Collections.singletonList( contextParam ) )
                         .build();
-                userIdentity = userSearchEngine.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
+                userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
             }
 
             if ( userIdentity == null )
             {
-                IntruderServiceClient.markAddressAndSession( pwmDomain, pwmSession );
+                IntruderServiceClient.markAddressAndSession( pwmRequest );
                 StatisticsClient.incrementStat( pwmRequest, Statistic.FORGOTTEN_USERNAME_FAILURES );
                 setLastError( pwmRequest, PwmError.ERROR_CANT_MATCH_USER.toInfo() );
                 forwardToFormJsp( pwmRequest );
@@ -230,7 +230,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
                     e.getErrorInformation().getFieldValues() )
                     : e.getErrorInformation();
             setLastError( pwmRequest, errorInfo );
-            IntruderServiceClient.markAddressAndSession( pwmDomain, pwmSession );
+            IntruderServiceClient.markAddressAndSession( pwmRequest );
             IntruderServiceClient.markAttributes( pwmDomain, formValues, pwmRequest.getLabel() );
         }
 

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

@@ -51,7 +51,7 @@ import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.FormMap;
@@ -318,11 +318,11 @@ public class GuestRegistrationServlet extends ControlledPwmServlet
                 .username( usernameParam )
                 .build();
 
-        final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+        final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
 
         try
         {
-            final UserIdentity theGuest = userSearchEngine.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
+            final UserIdentity theGuest = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
             final FormMap formProps = guBean.getFormValues();
             try
             {

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

@@ -286,8 +286,7 @@ public class SetupOtpServlet extends ControlledPwmServlet
         final OTPUserRecord otpUserRecord = pwmSession.getUserInfo().getOtpUserRecord();
         final OtpService otpService = pwmDomain.getOtpService();
 
-        final String bodyString = pwmRequest.readRequestBodyAsString();
-        final Map<String, String> clientValues = JsonFactory.get().deserializeStringMap( bodyString );
+        final Map<String, String> clientValues = pwmRequest.readBodyAsJsonStringMap(  );
         final String code = Validator.sanitizeInputValue( pwmRequest.getAppConfig(), clientValues.get( "code" ), 1024 );
 
         try

+ 5 - 5
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java

@@ -50,7 +50,7 @@ import password.pwm.i18n.Message;
 import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
@@ -260,7 +260,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
             // read an ldap user object based on the params
             final UserIdentity userIdentity;
             {
-                final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+                final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
                 final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                         .contexts( Collections.singletonList( contextParam ) )
                         .filter( searchFilter )
@@ -268,7 +268,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
                         .ldapProfile( ldapProfile )
                         .build();
 
-                userIdentity = userSearchEngine.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
+                userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
             }
 
             ActivateUserUtils.validateParamsAgainstLDAP( pwmRequest, formValues, userIdentity );
@@ -280,7 +280,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
         catch ( final PwmOperationalException e )
         {
             IntruderServiceClient.markAttributes( pwmRequest, formValues );
-            IntruderServiceClient.markAddressAndSession( pwmDomain, pwmSession );
+            IntruderServiceClient.markAddressAndSession( pwmRequest );
             setLastError( pwmRequest, e.getErrorInformation() );
             LOGGER.debug( pwmRequest, e.getErrorInformation() );
         }
@@ -484,7 +484,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
         {
             LOGGER.debug( pwmRequest, e.getErrorInformation() );
             IntruderServiceClient.markUserIdentity( pwmRequest, activateUserBean.getUserIdentity() );
-            IntruderServiceClient.markAddressAndSession( pwmDomain, pwmSession );
+            IntruderServiceClient.markAddressAndSession( pwmRequest );
             pwmRequest.respondWithError( e.getErrorInformation() );
         }
     }

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java

@@ -48,7 +48,7 @@ import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.event.AuditEventType;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.intruder.IntruderRecordType;
@@ -550,11 +550,11 @@ public class AdminServlet extends ControlledPwmServlet
             return;
         }
 
-        final UserSearchEngine userSearchEngine = pwmRequest.getPwmDomain().getUserSearchEngine();
+        final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine();
         final UserIdentity userIdentity;
         try
         {
-            userIdentity = userSearchEngine.resolveUsername( username, null, null, pwmRequest.getLabel() );
+            userIdentity = userSearchService.resolveUsername( username, null, null, pwmRequest.getLabel() );
             final AdminBean adminBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, AdminBean.class );
             adminBean.setLastUserDebug( userIdentity );
 

+ 3 - 5
server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -322,7 +322,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         }
         catch ( final PwmOperationalException e )
         {
-            IntruderServiceClient.markAddressAndSession( pwmRequest.getPwmDomain(), pwmSession );
+            IntruderServiceClient.markAddressAndSession( pwmRequest );
             IntruderServiceClient.markUserIdentity( pwmRequest, userInfo.getUserIdentity() );
             LOGGER.debug( pwmRequest, e.getErrorInformation() );
             setLastError( pwmRequest, e.getErrorInformation() );
@@ -426,10 +426,8 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     public ProcessStatus processCheckPasswordAction( final PwmRequest pwmRequest )
             throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
-        final RestCheckPasswordServer.JsonInput jsonInput = JsonFactory.get().deserialize(
-                pwmRequest.readRequestBodyAsString(),
-                RestCheckPasswordServer.JsonInput.class
-        );
+        final RestCheckPasswordServer.JsonInput jsonInput =
+                pwmRequest.readBodyAsJsonObject( RestCheckPasswordServer.JsonInput.class );
 
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final UserInfo userInfo = pwmSession.getUserInfo();

+ 11 - 13
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -246,8 +246,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final String bodyString = pwmRequest.readRequestBodyAsString();
-        final Map<String, String> requestMap = JsonFactory.get().deserializeStringMap( bodyString );
+        final Map<String, String> requestMap = pwmRequest.readBodyAsJsonStringMap();
         final PwmSetting pwmSetting = PwmSetting.forKey( requestMap.get( "setting" ) )
                 .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
         final String functionName = requestMap.get( "function" );
@@ -316,7 +315,6 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
-        final String bodyString = pwmRequest.readRequestBodyAsString();
         final UserIdentity loggedInUser = pwmRequest.getUserInfoIfLoggedIn();
 
         final ReadSettingResponse readSettingResponse;
@@ -325,7 +323,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
 
         if ( key.getRecordType() == StoredConfigKey.RecordType.LOCALE_BUNDLE )
         {
-            final Map<String, String> valueMap = JsonFactory.get().deserializeStringMap( bodyString );
+            final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap();
             final Map<String, String> outputMap = new LinkedHashMap<>( valueMap );
 
             final PwmLocaleBundle pwmLocaleBundle = key.toLocaleBundle();
@@ -337,6 +335,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         {
             try
             {
+                final String bodyString = pwmRequest.readRequestBodyAsString();
                 final StoredValue storedValue = ValueFactory.fromJson( key.toPwmSetting(), bodyString );
                 modifier.writeSetting( key, storedValue, loggedInUser );
             }
@@ -532,8 +531,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final String bodyData = pwmRequest.readRequestBodyAsString();
-        final Map<String, String> valueMap = JsonFactory.get().deserializeStringMap( bodyData );
+        final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap();
         final Locale locale = pwmRequest.getLocale();
         final RestResultBean restResultBean;
         final String searchTerm = valueMap.get( "search" );
@@ -580,7 +578,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         LOGGER.debug( pwmRequest, () -> "beginning restLdapHealthCheck" );
         final String profileID = pwmRequest.readParameterAsString( REQ_PARAM_PROFILE );
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.LDAP_SERVER_URLS );
-        final DomainConfig config = new AppConfig( configManagerBean.getStoredConfiguration() ).getDomainConfigs().get( domainID );
+        final DomainConfig config = AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() ).getDomainConfigs().get( domainID );
         final PublicHealthData healthData = LDAPHealthChecker.healthForNewConfiguration(
                 pwmRequest.getLabel(),
                 pwmRequest.getPwmDomain(),
@@ -605,7 +603,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final Instant startTime = Instant.now();
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         LOGGER.debug( pwmRequest, () -> "beginning restDatabaseHealthCheck" );
-        final AppConfig config = new AppConfig( configManagerBean.getStoredConfiguration() );
+        final AppConfig config = AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() );
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.LDAP_SERVER_URLS );
         final List<HealthRecord> healthRecords = DatabaseStatusChecker.checkNewDatabaseStatus( pwmRequest.getPwmApplication(), config );
         final PublicHealthData healthData = HealthRecord.asHealthDataBean( config.getDomainConfigs().get( domainID ), pwmRequest.getLocale(), healthRecords );
@@ -626,7 +624,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         LOGGER.debug( pwmRequest, () -> "beginning restSmsHealthCheck" );
 
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.LDAP_SERVER_URLS );
-        final DomainConfig config = new AppConfig( configManagerBean.getStoredConfiguration() ).getDomainConfigs().get( domainID );
+        final DomainConfig config = AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() ).getDomainConfigs().get( domainID );
         final StringBuilder output = new StringBuilder();
         output.append( "beginning SMS send process:\n" );
 
@@ -684,7 +682,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final StringBuilder output = new StringBuilder();
         output.append( "beginning EMail send process:\n" );
 
-        final AppConfig testDomainConfig = new AppConfig( configManagerBean.getStoredConfiguration() );
+        final AppConfig testDomainConfig = AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() );
 
         final EmailServerProfile emailServerProfile = testDomainConfig.getEmailServerProfiles().get( profileID );
         if ( emailServerProfile != null )
@@ -844,7 +842,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final String profile;
         {
             final String selectedProfile = inputMap.get( LdapBrowser.PARAM_PROFILE );
-            final AppConfig appConfig = new AppConfig( storedConfiguration );
+            final AppConfig appConfig = AppConfig.forStoredConfig( storedConfiguration );
             final DomainConfig domainConfig = appConfig.getDomainConfigs().getOrDefault( domainID, AppConfig.defaultConfig().getAdminDomain() );
             profile = domainConfig.getLdapProfiles().containsKey( selectedProfile )
                     ? selectedProfile
@@ -854,7 +852,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
 
         final LdapBrowser ldapBrowser = new LdapBrowser(
                 pwmRequest.getLabel(),
-                pwmRequest.getPwmDomain().getLdapConnectionService().getChaiProviderFactory(),
+                pwmRequest.getPwmDomain().getLdapService().getChaiProviderFactory(),
                 storedConfiguration
         );
 
@@ -951,7 +949,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     public ProcessStatus restRandomPassword( final PwmRequest pwmRequest )
             throws IOException, PwmUnrecoverableException
     {
-        final RestRandomPasswordServer.JsonInput jsonInput = JsonFactory.get().deserialize( pwmRequest.readRequestBodyAsString(), RestRandomPasswordServer.JsonInput.class );
+        final RestRandomPasswordServer.JsonInput jsonInput = pwmRequest.readBodyAsJsonObject( RestRandomPasswordServer.JsonInput.class );
         final RandomGeneratorConfig randomConfig = RestRandomPasswordServer.jsonInputToRandomConfig( jsonInput, pwmRequest.getPwmDomain(), PwmPasswordPolicy.defaultPolicy() );
         final PasswordData randomPassword = RandomPasswordGenerator.createRandomPassword( pwmRequest.getLabel(), randomConfig, pwmRequest.getPwmDomain() );
         final RestRandomPasswordServer.JsonOutput outputMap = new RestRandomPasswordServer.JsonOutput();

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

@@ -35,6 +35,7 @@ import password.pwm.config.stored.ValueMetaData;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.PrivateKeyValue;
 import password.pwm.config.value.X509CertificateValue;
+import password.pwm.data.FileUploadItem;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
@@ -42,6 +43,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.ConfigurationChecker;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmRequestUtil;
 import password.pwm.http.bean.ConfigManagerBean;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.PwmLocaleBundle;
@@ -78,10 +80,10 @@ public class ConfigEditorServletUtils
     )
             throws PwmUnrecoverableException, IOException
     {
-        final Map<String, PwmRequest.FileUploadItem> fileUploads;
+        final Map<String, FileUploadItem> fileUploads;
         try
         {
-            fileUploads = pwmRequest.readFileUploads( maxFileSize, 1 );
+            fileUploads = PwmRequestUtil.readFileUploads( pwmRequest, maxFileSize, 1 );
         }
         catch ( final PwmException e )
         {
@@ -98,7 +100,7 @@ public class ConfigEditorServletUtils
         }
 
         {
-            final PwmRequest.FileUploadItem uploadItem = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD );
+            final FileUploadItem uploadItem = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD );
             if ( uploadItem != null )
             {
                 return Optional.of( FileValue.newFileValue( uploadItem.getName(), uploadItem.getType(), uploadItem.getContent() ) );
@@ -141,7 +143,7 @@ public class ConfigEditorServletUtils
 
             final PwmApplication tempApplication = PwmApplication.createPwmApplication( pwmRequest.getPwmApplication()
                     .getPwmEnvironment()
-                    .makeRuntimeInstance( new AppConfig( configManagerBean.getStoredConfiguration() ) ) );
+                    .makeRuntimeInstance( AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() ) ) );
 
             final List<HealthRecord> healthRecords = configurationChecker.doHealthCheck( tempApplication, pwmRequest.getLabel() );
             final Map<DomainID, List<String>> returnData = new TreeMap<>();
@@ -292,7 +294,7 @@ public class ConfigEditorServletUtils
             }
 
             final int maxFileSize = Integer.parseInt( pwmRequest.getDomainConfig().readAppProperty( AppProperty.CONFIG_MAX_FILEVALUE_SIZE ) );
-            final Map<String, PwmRequest.FileUploadItem> fileUploads = pwmRequest.readFileUploads( maxFileSize, 1 );
+            final Map<String, FileUploadItem> fileUploads = PwmRequestUtil.readFileUploads( pwmRequest, maxFileSize, 1 );
             final InputStream fileIs = fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).getContent().newByteArrayInputStream();
 
             final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );

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

@@ -65,7 +65,7 @@ public class DomainStateReader
     {
         final ConfigManagerBean configManagerBean = ConfigEditorServlet.getBean( pwmRequest );
         final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
-        return new AppConfig( storedConfiguration );
+        return AppConfig.forStoredConfig( storedConfiguration );
     }
 
     public DomainManageMode getMode()

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

@@ -176,7 +176,7 @@ public class NavTreeDataMaker
             final StoredConfiguration storedConfiguration
     )
     {
-        final List<Locale> knownLocales = Collections.unmodifiableList( new AppConfig( storedConfiguration ).getKnownLocales() );
+        final List<Locale> knownLocales = Collections.unmodifiableList( AppConfig.forStoredConfig( storedConfiguration ).getKnownLocales() );
         final List<String> modifiedKeys = new ArrayList<>();
         for ( final String key : bundle.getDisplayKeys() )
         {
@@ -395,7 +395,7 @@ public class NavTreeDataMaker
         }
 
         if ( setting.getFlags().contains( PwmSettingFlag.MultiDomain )
-                && ( !( new AppConfig( storedConfiguration ).isMultiDomain() ) ) )
+                && ( !( AppConfig.forStoredConfig( storedConfiguration ).isMultiDomain() ) ) )
         {
             return false;
         }

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

@@ -63,7 +63,7 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction
             }
             else
             {
-                final AppConfig appConfig = new AppConfig( modifier.newStoredConfiguration() );
+                final AppConfig appConfig = AppConfig.forStoredConfig( modifier.newStoredConfiguration() );
                 certs = X509Utils.readRemoteCertificates( URI.create( urlString ), appConfig );
             }
         }

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

@@ -111,7 +111,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
     )
             throws PwmUnrecoverableException, PwmOperationalException
     {
-        final AppConfig config = new AppConfig( storedConfiguration );
+        final AppConfig config = AppConfig.forStoredConfig( storedConfiguration );
         final PwmApplication tempApplication = PwmApplication.createPwmApplication( pwmDomain.getPwmApplication().getPwmEnvironment().makeRuntimeInstance( config ) );
         final StoredValue storedValue = StoredConfigurationUtil.getValueOrDefault( storedConfiguration, key );
         final List<UserPermission> permissions = ValueTypeConverter.valueToUserPermissions( storedValue );

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -190,7 +190,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
                 final URI ldapServerUri = new URI( ldapServerString );
                 if ( "ldaps".equalsIgnoreCase( ldapServerUri.getScheme() ) )
                 {
-                    final AppConfig tempConfig = new AppConfig( ConfigGuideForm.generateStoredConfig( configGuideBean ) );
+                    final AppConfig tempConfig = AppConfig.forStoredConfig( ConfigGuideForm.generateStoredConfig( configGuideBean ) );
                     configGuideBean.setLdapCertificates( X509Utils.readRemoteCertificates( ldapServerUri, tempConfig ) );
                     configGuideBean.setCertsTrustedbyKeystore( X509Utils.testIfLdapServerCertsInDefaultKeystore( ldapServerUri ) );
                 }
@@ -240,7 +240,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         final ConfigGuideBean configGuideBean = getBean( pwmRequest );
 
         final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
-        final AppConfig tempAppConfig = new AppConfig( storedConfiguration );
+        final AppConfig tempAppConfig = AppConfig.forStoredConfig( storedConfiguration );
         final PwmApplication tempApplication = PwmApplication.createPwmApplication( pwmRequest.getPwmApplication()
                 .getPwmEnvironment()
                 .makeRuntimeInstance( tempAppConfig ) );
@@ -367,7 +367,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
 
         final LdapBrowser ldapBrowser = new LdapBrowser(
                 pwmRequest.getLabel(),
-                pwmRequest.getPwmDomain().getLdapConnectionService().getChaiProviderFactory(),
+                pwmRequest.getPwmDomain().getLdapService().getChaiProviderFactory(),
                 storedConfiguration
         );
         final LdapBrowser.LdapBrowseResult result = ldapBrowser.doBrowse( domainID, ConfigGuideForm.LDAP_PROFILE_NAME, dn );

+ 5 - 5
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java

@@ -161,7 +161,7 @@ public class ConfigGuideUtils
                     .setSetting( ChaiSetting.PROMISCUOUS_SSL, "true" )
                     .build();
 
-            final ChaiProvider chaiProvider = pwmDomain.getLdapConnectionService().getChaiProviderFactory().newProvider( chaiConfiguration );
+            final ChaiProvider chaiProvider = pwmDomain.getLdapService().getChaiProviderFactory().newProvider( chaiConfiguration );
             if ( doSchemaExtension )
             {
                 return SchemaManager.extendSchema( chaiProvider );
@@ -188,7 +188,7 @@ public class ConfigGuideUtils
         if ( configGuideBean.getStep() == GuideStep.LDAP_PERMISSIONS )
         {
             final LdapPermissionCalculator ldapPermissionCalculator = new LdapPermissionCalculator(
-                    new AppConfig( ConfigGuideForm.generateStoredConfig( configGuideBean ) ).getDomainConfigs().get( ConfigGuideForm.DOMAIN_ID ) );
+                    AppConfig.forStoredConfig( ConfigGuideForm.generateStoredConfig( configGuideBean ) ).getDomainConfigs().get( ConfigGuideForm.DOMAIN_ID ) );
             pwmRequest.setAttribute( PwmRequestAttribute.LdapPermissionItems, ldapPermissionCalculator );
         }
 
@@ -225,7 +225,7 @@ public class ConfigGuideUtils
 
         if ( Boolean.parseBoolean( formData.get( ConfigGuideFormField.PARAM_LDAP_SECURE ) ) )
         {
-            final AppConfig tempConfig = new AppConfig( ConfigGuideForm.generateStoredConfig( configGuideBean ) );
+            final AppConfig tempConfig = AppConfig.forStoredConfig( ConfigGuideForm.generateStoredConfig( configGuideBean ) );
             X509Utils.readRemoteCertificates( host, port, tempConfig );
         }
     }
@@ -261,7 +261,7 @@ public class ConfigGuideUtils
                     {
                         throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, configErrors.get( 0 ) ) );
                     }
-                    ConfigGuideUtils.writeConfig( ContextManager.getContextManager( req.getSession() ), storedConfig );
+                    ConfigGuideUtils.writeConfig( ContextManager.getContextManager( pwmRequest ), storedConfig );
                     LOGGER.trace( pwmRequest, () -> "read config from file: " + storedConfig );
                     final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown );
                     pwmRequest.getPwmResponse().outputJsonResult( restResultBean );
@@ -293,7 +293,7 @@ public class ConfigGuideUtils
             final ConfigGuideBean configGuideBean = ConfigGuideServlet.getBean( pwmRequest );
             final Map<ConfigGuideFormField, String> form = configGuideBean.getFormData();
             final PwmApplication tempApplication = PwmApplication.createPwmApplication(
-                    pwmRequest.getPwmApplication().getPwmEnvironment().makeRuntimeInstance( new AppConfig( storedConfiguration ) ) );
+                    pwmRequest.getPwmApplication().getPwmEnvironment().makeRuntimeInstance( AppConfig.forStoredConfig( storedConfiguration ) ) );
 
             final String adminDN = form.get( ConfigGuideFormField.PARAM_LDAP_ADMIN_USER );
             final UserIdentity adminIdentity = UserIdentity.create( adminDN, ConfigGuideForm.LDAP_PROFILE_NAME, ConfigGuideForm.DOMAIN_ID );

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java

@@ -138,7 +138,7 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
             throws PwmUnrecoverableException, IOException, ServletException
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest ).getConfigReader();
         final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration();
 
         final String password = pwmRequest.readParameterAsString( "password" );
@@ -153,7 +153,7 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
             else
             {
                 LOGGER.trace( pwmRequest, () -> "configuration password is not correct" );
-                IntruderServiceClient.markAddressAndSession( pwmDomain, pwmRequest.getPwmSession() );
+                IntruderServiceClient.markAddressAndSession( pwmRequest );
                 pwmDomain.getIntruderService().mark( IntruderRecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmRequest.getLabel() );
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_PASSWORD_ONLY_BAD );
                 updateLoginHistory( pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), false );
@@ -326,7 +326,7 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
             return;
         }
 
-        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final ConfigurationFileManager runningConfigReader = ContextManager.getContextManager( pwmRequest ).getConfigReader();
         final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration();
 
         try

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -395,7 +395,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet
             throws IOException, ServletException, PwmUnrecoverableException
     {
         final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest );
-        final AppConfig appConfig = new AppConfig( storedConfiguration );
+        final AppConfig appConfig = AppConfig.forStoredConfig( storedConfiguration );
         final LdapPermissionCalculator ldapPermissionCalculator = new LdapPermissionCalculator( appConfig.getDomainConfigs().get( pwmRequest.getDomainID() ) );
         pwmRequest.setAttribute( PwmRequestAttribute.LdapPermissionItems, ldapPermissionCalculator );
         pwmRequest.forwardToJsp( JspUrl.CONFIG_MANAGER_PERMISSIONS );

+ 7 - 7
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -69,7 +69,7 @@ import password.pwm.ldap.auth.AuthenticationUtility;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
@@ -449,7 +449,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             // convert the username field to an identity
             final UserIdentity userIdentity;
             {
-                final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+                final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
                 final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                         .filter( searchFilter )
                         .formValues( formValues )
@@ -457,7 +457,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                         .ldapProfile( ldapProfile )
                         .build();
 
-                userIdentity = userSearchEngine.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
+                userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
             }
 
             if ( userIdentity == null )
@@ -486,7 +486,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
                 StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_FAILURES );
 
-                IntruderServiceClient.markAddressAndSession( pwmDomain, pwmRequest.getPwmSession() );
+                IntruderServiceClient.markAddressAndSession( pwmRequest );
                 IntruderServiceClient.markAttributes( pwmRequest, formValues );
 
                 LOGGER.debug( pwmRequest, errorInfo );
@@ -688,10 +688,10 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
         final UserIdentity oauthUserIdentity;
         {
-            final UserSearchEngine userSearchEngine = pwmRequest.getPwmDomain().getUserSearchEngine();
+            final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine();
             try
             {
-                oauthUserIdentity = userSearchEngine.resolveUsername( userDNfromOAuth, null, null, pwmRequest.getLabel() );
+                oauthUserIdentity = userSearchService.resolveUsername( userDNfromOAuth, null, null, pwmRequest.getLabel() );
             }
             catch ( final PwmOperationalException e )
             {
@@ -1286,7 +1286,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             IntruderServiceClient.markUserIdentity( pwmRequest, userIdentity );
         }
 
-        IntruderServiceClient.markAddressAndSession( pwmRequest.getPwmDomain(), pwmRequest.getPwmSession() );
+        IntruderServiceClient.markAddressAndSession( pwmRequest );
 
         StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_FAILURES );
     }

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java

@@ -60,7 +60,7 @@ import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.auth.AuthenticationUtility;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.intruder.IntruderServiceClient;
 import password.pwm.svc.otp.OTPUserRecord;
 import password.pwm.svc.stats.Statistic;
@@ -1001,7 +1001,7 @@ public class ForgottenPasswordStateMachine
 
                 // convert the username field to an identity
                 {
-                    final UserSearchEngine userSearchEngine = pwmRequestContext.getPwmDomain().getUserSearchEngine();
+                    final UserSearchService userSearchService = pwmRequestContext.getPwmDomain().getUserSearchEngine();
                     final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                             .filter( searchFilter )
                             .formValues( formValues )
@@ -1009,7 +1009,7 @@ public class ForgottenPasswordStateMachine
                             .ldapProfile( ldapProfile.getIdentifier() )
                             .build();
 
-                    userIdentity = userSearchEngine.performSingleUserSearch( searchConfiguration, pwmRequestContext.getSessionLabel() );
+                    userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequestContext.getSessionLabel() );
                 }
 
                 if ( userIdentity == null )

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskCardInfoBean.java

@@ -82,7 +82,7 @@ public class HelpdeskCardInfoBean implements Serializable
                 theUser.getChaiProvider()
         );
 
-        builder.userKey( userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
+        builder.userKey( HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, userIdentity ) );
 
         final PhotoDataReader photoDataReader = HelpdeskServlet.photoDataReader( pwmRequest, helpdeskProfile, userIdentity );
         final Optional<String> optionalPhotoUrl = photoDataReader.figurePhotoURL();

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java

@@ -148,7 +148,7 @@ public class HelpdeskDetailInfoBean implements Serializable
             LOGGER.error( pwmRequest, () -> "unexpected error reading userHistory for user '" + userIdentity + "', " + e.getMessage() );
         }
 
-        builder.userKey( userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
+        builder.userKey( HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, userIdentity ) );
 
         builder.profileData( getProfileData( helpdeskProfile, userInfo, pwmRequest.getLabel(), pwmRequest.getLocale() ) );
 

+ 31 - 41
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -64,8 +64,8 @@ import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchResults;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.cr.CrService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
@@ -240,7 +240,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
     public ProcessStatus processExecuteActionRequest(
             final PwmRequest pwmRequest
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
+            throws PwmUnrecoverableException, IOException, ServletException
     {
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
         final String userKey = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation ).get( PwmConstants.PARAM_USERKEY );
@@ -251,7 +251,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             pwmRequest.respondWithError( errorInformation, false );
             return ProcessStatus.Halt;
         }
-        final UserIdentity targetUserIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity targetUserIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey );
         LOGGER.debug( pwmRequest, () -> "received executeAction request for user " + targetUserIdentity.toString() );
 
         final List<ActionConfiguration> actionConfigurations = helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_ACTIONS );
@@ -341,7 +341,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             return ProcessStatus.Halt;
         }
 
-        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey );
         LOGGER.info( pwmRequest, () -> "received deleteUser request by " + pwmSession.getUserInfo().getUserIdentity().toString() + " for user " + userIdentity.toString() );
 
         // check if user should be seen by actor
@@ -450,7 +450,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException, IOException
     {
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
-        final HelpdeskSearchRequestBean searchRequest = JsonFactory.get().deserialize( pwmRequest.readRequestBodyAsString(), HelpdeskSearchRequestBean.class );
+        final HelpdeskSearchRequestBean searchRequest = pwmRequest.readBodyAsJsonObject( HelpdeskSearchRequestBean.class );
         final HelpdeskSearchResultsBean searchResultsBean;
 
         try
@@ -472,7 +472,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,
             final HelpdeskSearchRequestBean searchRequest
-    ) throws PwmUnrecoverableException, PwmOperationalException
+    )
+            throws PwmUnrecoverableException, PwmOperationalException
     {
 
         final boolean useProxy = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY );
@@ -510,7 +511,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 MiscUtil.unhandledSwitchStatement( searchMode );
         }
 
-        final UserSearchEngine userSearchEngine = pwmRequest.getPwmDomain().getUserSearchEngine();
+        final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine();
 
 
         final SearchConfiguration searchConfiguration;
@@ -570,12 +571,16 @@ public class HelpdeskServlet extends ControlledPwmServlet
         final boolean sizeExceeded;
         {
             final Locale locale = pwmRequest.getLocale();
-            results = userSearchEngine.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() );
+            results = userSearchService.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() );
             sizeExceeded = results.isSizeExceeded();
         }
 
+        final List<Map<String, Object>> jsonResults = results.resultsAsJsonOutput(
+                userIdentity -> HelpdeskServletUtil.obfuscateUserIdentity( pwmRequest, userIdentity ),
+                pwmRequest.getUserInfoIfLoggedIn() );
+
         return HelpdeskSearchResultsBean.builder()
-                .searchResults( results.resultsAsJsonOutput( pwmRequest.getPwmDomain(), pwmRequest.getUserInfoIfLoggedIn() ) )
+                .searchResults( jsonResults )
                 .sizeExceeded( sizeExceeded )
                 .build();
     }
@@ -594,7 +599,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             pwmRequest.respondWithError( errorInformation, false );
             return ProcessStatus.Halt;
         }
-        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey );
 
         if ( !helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_UNLOCK ) )
         {
@@ -670,7 +675,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             pwmRequest.respondWithError( errorInformation, false );
             return ProcessStatus.Halt;
         }
-        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey );
 
         if ( !helpdeskProfile.readOptionalVerificationMethods().contains( IdentityVerificationMethod.OTP ) )
         {
@@ -748,10 +753,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
         final DomainConfig config = pwmRequest.getDomainConfig();
         final Map<String, String> bodyParams = pwmRequest.readBodyAsJsonStringMap();
 
-        final UserIdentity targetUserIdentity = UserIdentity.fromKey(
-                pwmRequest.getLabel(),
-                bodyParams.get( PwmConstants.PARAM_USERKEY ),
-                pwmRequest.getPwmApplication() );
+        final UserIdentity targetUserIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, bodyParams.get( PwmConstants.PARAM_USERKEY ) );
         final UserInfo targetUserInfo = HelpdeskServletUtil.getTargetUserInfo( pwmRequest, helpdeskProfile, targetUserIdentity );
 
         final String requestedTokenID = bodyParams.get( "id" );
@@ -846,10 +848,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = JsonFactory.get().deserialize(
-                pwmRequest.readRequestBodyAsString(),
-                HelpdeskVerificationRequestBean.class
-        );
+        final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean =
+                pwmRequest.readBodyAsJsonObject( HelpdeskVerificationRequestBean.class );
         final String token = helpdeskVerificationRequestBean.getCode();
 
         final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService();
@@ -858,10 +858,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 HelpdeskVerificationRequestBean.TokenData.class
         );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey(
-                pwmRequest.getLabel(),
-                helpdeskVerificationRequestBean.getUserKey(),
-                pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity(
+                pwmRequest, helpdeskVerificationRequestBean.getUserKey() );
 
         if ( tokenData == null || tokenData.getIssueDate() == null || tokenData.getToken() == null || tokenData.getToken().isEmpty() )
         {
@@ -1030,10 +1028,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 HelpdeskVerificationRequestBean.class
         );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey(
-                pwmRequest.getLabel(),
-                helpdeskVerificationRequestBean.getUserKey(),
-                pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest,
+                helpdeskVerificationRequestBean.getUserKey() );
 
         boolean passed = false;
         {
@@ -1196,13 +1192,9 @@ public class HelpdeskServlet extends ControlledPwmServlet
     @ActionHandler( action = "checkPassword" )
     public ProcessStatus processCheckPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
-        final RestCheckPasswordServer.JsonInput jsonInput = JsonFactory.get().deserialize(
-                pwmRequest.readRequestBodyAsString(),
-                RestCheckPasswordServer.JsonInput.class
-        );
+        final RestCheckPasswordServer.JsonInput jsonInput = pwmRequest.readBodyAsJsonObject( RestCheckPasswordServer.JsonInput.class );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey(
-                pwmRequest.getLabel(), jsonInput.getUsername(), pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, jsonInput.getUsername() );
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
 
         HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity );
@@ -1248,12 +1240,10 @@ public class HelpdeskServlet extends ControlledPwmServlet
     {
         final HelpdeskProfile helpdeskProfile = pwmRequest.getHelpdeskProfile( );
 
-        final RestSetPasswordServer.JsonInputData jsonInput = JsonFactory.get().deserialize(
-                pwmRequest.readRequestBodyAsString(),
-                RestSetPasswordServer.JsonInputData.class
-        );
+        final RestSetPasswordServer.JsonInputData jsonInput =
+                pwmRequest.readBodyAsJsonObject( RestSetPasswordServer.JsonInputData.class );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), jsonInput.getUsername(), pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, jsonInput.getUsername() );
         final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity );
         final UserInfo userInfo = UserInfoFactory.newUserInfo(
                 pwmRequest.getPwmApplication(),
@@ -1331,8 +1321,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
     @ActionHandler( action = "randomPassword" )
     public ProcessStatus processRandomPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
-        final RestRandomPasswordServer.JsonInput input = JsonFactory.get().deserialize( pwmRequest.readRequestBodyAsString(), RestRandomPasswordServer.JsonInput.class );
-        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), input.getUsername(), pwmRequest.getPwmApplication() );
+        final RestRandomPasswordServer.JsonInput input = pwmRequest.readBodyAsJsonObject( RestRandomPasswordServer.JsonInput.class );
+        final UserIdentity userIdentity = HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, input.getUsername() );
 
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
 
@@ -1383,7 +1373,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" );
             throw new PwmUnrecoverableException( errorInformation );
         }
-        return UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
+        return HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey );
     }
 
     static PhotoDataReader photoDataReader( final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity )

+ 27 - 4
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java

@@ -37,7 +37,6 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.ldap.permission.UserPermissionUtility;
@@ -47,6 +46,7 @@ import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.event.HelpdeskAuditRecord;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
+import password.pwm.user.UserInfo;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -56,6 +56,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 
 public class HelpdeskServletUtil
 {
@@ -247,16 +248,17 @@ public class HelpdeskServletUtil
         return helpdeskDetailInfoBean;
     }
 
-    static UserIdentity userIdentityFromMap( final PwmRequest pwmRequest, final Map<String, String> bodyMap ) throws PwmUnrecoverableException
+    static UserIdentity userIdentityFromMap( final PwmRequest pwmRequest, final Map<String, String> bodyMap )
+            throws PwmUnrecoverableException
     {
         final String userKey = bodyMap.get( "userKey" );
-        if ( userKey == null || userKey.length() < 1 )
+        if ( StringUtil.isEmpty( userKey ) )
         {
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" );
             throw new PwmUnrecoverableException( errorInformation );
         }
 
-        return UserIdentity.fromObfuscatedKey( userKey, pwmRequest.getPwmApplication() );
+        return HelpdeskServletUtil.clarifyUserIdentity( pwmRequest, userKey );
     }
 
 
@@ -349,6 +351,27 @@ public class HelpdeskServletUtil
         );
     }
 
+    static String obfuscateUserIdentity( final PwmRequest pwmRequest, final UserIdentity userIdentity )
+    {
+        try
+        {
+            return pwmRequest.encryptObjectToString( userIdentity );
+        }
+        catch ( final PwmUnrecoverableException e )
+        {
+            throw new IllegalStateException( "unexpected error encoding userIdentity: " + e.getMessage() );
+        }
+    }
+
+    static UserIdentity clarifyUserIdentity( final PwmRequest pwmRequest, final String input )
+            throws PwmUnrecoverableException
+    {
+        Objects.requireNonNull( input );
+
+        return pwmRequest.decryptObject( input, UserIdentity.class );
+    }
+
+
     static MacroRequest getTargetUserMacroRequest(
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,

+ 4 - 4
server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -60,7 +60,7 @@ import password.pwm.user.UserInfoBean;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.stats.Statistic;
@@ -260,7 +260,7 @@ class NewUserUtils
                         .setSetting( ChaiSetting.BIND_DN, newUserDN )
                         .setSetting( ChaiSetting.BIND_PASSWORD, temporaryPassword.getStringValue() )
                         .build();
-                final ChaiProvider bindAsProvider = pwmDomain.getLdapConnectionService().getChaiProviderFactory().newProvider( chaiConfiguration );
+                final ChaiProvider bindAsProvider = pwmDomain.getLdapService().getChaiProviderFactory().newProvider( chaiConfiguration );
                 final ChaiUser bindAsUser = bindAsProvider.getEntryFactory().newChaiUser( newUserDN );
                 bindAsUser.changePassword( temporaryPassword.getStringValue(), userPassword.getStringValue() );
                 NewUserUtils.LOGGER.debug( pwmRequest, () -> "changed to user requested password for new user entry: " + newUserDN );
@@ -438,14 +438,14 @@ class NewUserUtils
     )
             throws PwmUnrecoverableException, ChaiUnavailableException
     {
-        final UserSearchEngine userSearchEngine = pwmRequest.getPwmDomain().getUserSearchEngine();
+        final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine();
         final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                 .username( rdnValue )
                 .build();
 
         try
         {
-            final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
+            final Map<UserIdentity, Map<String, String>> results = userSearchService.performMultiUserSearch(
                     searchConfiguration, 2, Collections.emptyList(), pwmRequest.getLabel() );
             return results != null && !results.isEmpty();
         }

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java

@@ -40,7 +40,7 @@ import password.pwm.http.servlet.forgottenpw.ForgottenPasswordServlet;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.util.java.MiscUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
@@ -248,8 +248,8 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
         {
             try
             {
-                final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
-                final UserIdentity resolvedIdentity = userSearchEngine.resolveUsername(
+                final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
+                final UserIdentity resolvedIdentity = userSearchService.resolveUsername(
                         oauthSuppliedUsername,
                         null,
                         null,

+ 2 - 4
server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java

@@ -86,8 +86,7 @@ public class OAuthMachine
         final String requestStateStr = pwmRequest.readParameterAsString( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_PARAM_OAUTH_STATE ) );
         if ( requestStateStr != null )
         {
-            final String stateJson = pwmRequest.getPwmDomain().getSecureService().decryptStringValue( requestStateStr );
-            final OAuthState oAuthState = JsonFactory.get().deserialize( stateJson, OAuthState.class );
+            final OAuthState oAuthState = pwmRequest.decryptObject( requestStateStr, OAuthState.class );
             if ( oAuthState != null )
             {
                 final boolean sessionMatch = oAuthState.getSessionID().equals( pwmRequest.getPwmSession().getSessionStateBean().getSessionVerificationKey() );
@@ -430,8 +429,7 @@ public class OAuthMachine
                 + oAuthState.getStateID() + " with the next destination URL set to " + oAuthState.getNextUrl() );
 
 
-        final String jsonValue = JsonFactory.get().serialize( oAuthState );
-        return pwmRequest.getPwmDomain().getSecureService().encryptToString( jsonValue );
+        return pwmRequest.encryptObjectToString( oAuthState );
     }
 
     private Optional<String> figureUsernameGrantParam(

+ 17 - 12
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java

@@ -30,6 +30,7 @@ import org.apache.commons.csv.CSVPrinter;
 import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
+import password.pwm.bean.PhotoDataBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.PeopleSearchProfile;
@@ -49,19 +50,18 @@ import password.pwm.http.servlet.peoplesearch.bean.SearchResultBean;
 import password.pwm.http.servlet.peoplesearch.bean.UserDetailBean;
 import password.pwm.http.servlet.peoplesearch.bean.UserReferenceBean;
 import password.pwm.i18n.Display;
-import password.pwm.bean.PhotoDataBean;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchResults;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CacheLoader;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
+import password.pwm.user.UserInfo;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.MiscUtil;
@@ -266,7 +266,7 @@ class PeopleSearchDataReader
         final Map<String, String> searchResults = detailResults.getResults().get( userIdentity );
 
         final UserDetailBean userDetailBean = new UserDetailBean();
-        userDetailBean.setUserKey( userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
+        userDetailBean.setUserKey( PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ) );
         final List<FormConfiguration> detailFormConfig = this.peopleSearchConfiguration.getSearchDetailForm();
         final Map<String, AttributeDetailBean> attributeBeans = convertResultMapToBeans( userIdentity, detailFormConfig, searchResults );
 
@@ -389,7 +389,7 @@ class PeopleSearchDataReader
             throws PwmUnrecoverableException
     {
         final OrgChartReferenceBean orgChartReferenceBean = new OrgChartReferenceBean();
-        orgChartReferenceBean.setUserKey( userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
+        orgChartReferenceBean.setUserKey( PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ) );
         final PhotoDataReader photoDataReader = photoDataReader( userIdentity );
         photoDataReader.figurePhotoURL( ).ifPresent( orgChartReferenceBean::setPhotoURL );
 
@@ -552,7 +552,7 @@ class PeopleSearchDataReader
                         {
                             final String displayValue = figureDisplaynameValue( pwmRequest, loopIdentity );
                             final UserReferenceBean userReference = new UserReferenceBean();
-                            userReference.setUserKey( loopIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
+                            userReference.setUserKey( PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, loopIdentity ) );
                             userReference.setDisplayName( displayValue );
                             userReferences.put( displayValue, userReference );
                         }
@@ -769,7 +769,7 @@ class PeopleSearchDataReader
         }
 
 
-        final UserSearchEngine userSearchEngine = pwmRequest.getPwmDomain().getUserSearchEngine();
+        final UserSearchService userSearchService = pwmRequest.getPwmDomain().getUserSearchEngine();
 
         final UserSearchResults results;
         final boolean sizeExceeded;
@@ -778,7 +778,7 @@ class PeopleSearchDataReader
             final List<FormConfiguration> searchForm = peopleSearchConfiguration.getResultForm();
             final int maxResults = peopleSearchConfiguration.getResultLimit();
             final Locale locale = pwmRequest.getLocale();
-            results = userSearchEngine.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() );
+            results = userSearchService.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getLabel() );
             sizeExceeded = results.isSizeExceeded();
         }
         catch ( final PwmOperationalException e )
@@ -788,7 +788,9 @@ class PeopleSearchDataReader
             throw new PwmUnrecoverableException( errorInformation );
         }
 
-        final List<Map<String, Object>> resultOutput = new ArrayList<>( results.resultsAsJsonOutput( pwmRequest.getPwmDomain(), null ) );
+        final List<Map<String, Object>> resultOutput = new ArrayList<>(
+                results.resultsAsJsonOutput( userIdentity -> PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ), null ) );
+
         if ( searchRequest.isIncludeDisplayName() )
         {
             for ( final Map<String, Object> map : resultOutput )
@@ -796,9 +798,12 @@ class PeopleSearchDataReader
                 final String userKey = ( String ) map.get( "userKey" );
                 if ( userKey != null )
                 {
-                    final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
-                    final String displayValue = figureDisplaynameValue( pwmRequest, userIdentity );
-                    map.put( "_displayName", displayValue );
+                    final Optional<UserIdentity> userIdentity = PeopleSearchServlet.clarifyUserIdentity( pwmRequest, userKey );
+                    if ( userIdentity.isPresent() )
+                    {
+                        final String displayValue = figureDisplaynameValue( pwmRequest, userIdentity.get() );
+                        map.put( "_displayName", displayValue );
+                    }
                 }
             }
         }

+ 40 - 4
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -177,7 +177,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
             }
             else
             {
-                userIdentity = UserIdentity.fromObfuscatedKey( userKey, pwmRequest.getPwmApplication() );
+                userIdentity = PeopleSearchServlet.clarifyUserIdentity( pwmRequest, userKey ).orElseThrow();
             }
         }
 
@@ -348,8 +348,44 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
 
         final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
         final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
-        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
-        peopleSearchDataReader.checkIfUserIdentityViewable( userIdentity );
-        return userIdentity;
+        final Optional<UserIdentity> userIdentity = PeopleSearchServlet.clarifyUserIdentity( pwmRequest, userKey );
+        if ( userIdentity.isPresent() )
+        {
+            peopleSearchDataReader.checkIfUserIdentityViewable( userIdentity.get() );
+            return userIdentity.get();
+        }
+
+        final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, PARAM_USERKEY + " parameter is invalid" );
+        LOGGER.error( pwmRequest, errorInformation );
+        throw new PwmUnrecoverableException( errorInformation );
+    }
+
+    static String obfuscateUserIdentity( final PwmRequest pwmRequest, final UserIdentity userIdentity )
+    {
+        try
+        {
+            return pwmRequest.encryptObjectToString( userIdentity );
+        }
+        catch ( final PwmUnrecoverableException e )
+        {
+            throw new IllegalStateException( "unexpected error encoding userIdentity: " + e.getMessage() );
+        }
+    }
+
+    static Optional<UserIdentity> clarifyUserIdentity( final PwmRequest pwmRequest, final String input )
+    {
+        if ( !StringUtil.isEmpty( input ) )
+        {
+            try
+            {
+                return Optional.of( pwmRequest.decryptObject( input, UserIdentity.class ) );
+            }
+            catch ( final PwmUnrecoverableException e )
+            {
+                LOGGER.debug( pwmRequest, () -> "error clarifying obfuscated userIdentity in request: " + e.getMessage() );
+            }
+        }
+
+        return Optional.empty();
     }
 }

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java

@@ -154,7 +154,7 @@ public class PhotoDataReader
             case ServerHttp:
                 String returnUrl = pwmRequest.getURLwithoutQueryString();
                 returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_ACTION_REQUEST, PeopleSearchServlet.PeopleSearchActions.photo.name() );
-                returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_USERKEY,  userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
+                returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_USERKEY,  PeopleSearchServlet.obfuscateUserIdentity( pwmRequest, userIdentity ) );
                 return Optional.of( returnUrl );
 
             default:

+ 77 - 0
server/src/main/java/password/pwm/http/servlet/resource/TextFileResource.java

@@ -0,0 +1,77 @@
+/*
+ * 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.http.servlet.resource;
+
+import password.pwm.PwmConstants;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ContextManager;
+import password.pwm.http.PwmRequest;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroRequest;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Optional;
+
+public enum TextFileResource
+{
+    eula( "eula.txt" ),
+    welcome( "welcome.txt" ),
+    privacy( "privacy.txt" ),;
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass( TextFileResource.class );
+
+    private final String filename;
+
+    TextFileResource( final String filename )
+    {
+        this.filename = filename;
+    }
+
+    public String getFilename()
+    {
+        return filename;
+    }
+
+    public static Optional<String> readTextFileResource( final PwmRequest pwmRequest, final TextFileResource resourceName )
+            throws PwmUnrecoverableException
+    {
+        try
+        {
+            final String path = PwmConstants.URL_PREFIX_PUBLIC + "/resources/text/" + resourceName.getFilename();
+            final InputStream inputStream = ContextManager.getContextManager( pwmRequest ).getResourceAsStream( path );
+            final Optional<String> rawValue = JavaHelper.copyToString( inputStream, PwmConstants.DEFAULT_CHARSET, 10_000_000 );
+            if ( rawValue.isPresent() )
+            {
+                final MacroRequest macroMachine = pwmRequest.getMacroMachine();
+                return Optional.of( macroMachine.expandMacros( rawValue.get() ) );
+            }
+            return Optional.empty();
+        }
+        catch ( final IOException e )
+        {
+            LOGGER.error( pwmRequest, () -> "error reading resource text file " + resourceName.getFilename() + ", error: " + e.getMessage() );
+        }
+
+        return Optional.empty();
+    }
+}

+ 3 - 17
server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java

@@ -31,7 +31,6 @@ import password.pwm.svc.secure.DomainSecureService;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -60,11 +59,10 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
 
         try
         {
-            final Optional<String> rawValue = pwmRequest.readCookie( cookieName );
-            final PwmSecurityKey key = keyForSession( pwmRequest );
+            final Optional<E> rawValue = pwmRequest.readEncryptedCookie( cookieName, theClass );
             if ( rawValue.isPresent() )
             {
-                final E cookieBean = pwmRequest.getPwmDomain().getSecureService().decryptObject( rawValue.get(), key, theClass );
+                final E cookieBean = rawValue.get();
                 if ( validateCookie( pwmRequest, cookieName, cookieBean ) )
                 {
                     sessionBeans.put( theClass, cookieBean );
@@ -149,9 +147,7 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
                     }
                     else
                     {
-                        final PwmSecurityKey key = keyForSession( pwmRequest );
-                        final String encryptedValue = pwmRequest.getPwmDomain().getSecureService().encryptObjectToString( entry.getValue(), key );
-                        pwmRequest.getPwmResponse().writeCookie( cookieName, encryptedValue, -1, COOKIE_PATH );
+                        pwmRequest.getPwmResponse().writeEncryptedCookie( cookieName, entry.getValue(), -1, COOKIE_PATH );
                     }
                 }
             }
@@ -193,14 +189,4 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
     {
         return null;
     }
-
-    private PwmSecurityKey keyForSession( final PwmRequest pwmRequest )
-            throws PwmUnrecoverableException
-    {
-        final PwmSecurityKey pwmSecurityKey = pwmRequest.getDomainConfig().getSecurityKey();
-        final String keyHash = pwmSecurityKey.keyHash( pwmRequest.getPwmDomain().getSecureService() );
-        final String userGuid = pwmRequest.getPwmSession().getLoginInfoBean().getGuid();
-        final String keyData = keyHash + pwmRequest.getPwmDomain().getSecureService().ephemeralHmac( userGuid );
-        return new PwmSecurityKey( keyData );
-    }
 }

+ 83 - 0
server/src/main/java/password/pwm/http/tag/PwmTextFileTag.java

@@ -0,0 +1,83 @@
+/*
+ * 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.http.tag;
+
+import password.pwm.http.JspUtility;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.servlet.resource.TextFileResource;
+import password.pwm.http.tag.value.PwmValueTag;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.servlet.jsp.tagext.TagSupport;
+import java.io.IOException;
+import java.util.Optional;
+
+public class PwmTextFileTag extends TagSupport
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmValueTag.class );
+
+    private TextFileResource textFileResource;
+
+    public TextFileResource getTextFileResource()
+    {
+        return textFileResource;
+    }
+
+    public void setTextFileResource( final TextFileResource textFileResource )
+    {
+        this.textFileResource = textFileResource;
+    }
+
+    @Override
+    public int doEndTag( )
+            throws javax.servlet.jsp.JspTagException
+    {
+        if ( textFileResource == null )
+        {
+            return EVAL_PAGE;
+        }
+
+        try
+        {
+            final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
+            final Optional<String> output = TextFileResource.readTextFileResource( pwmRequest, getTextFileResource() );
+
+            if ( output.isPresent() )
+            {
+                pageContext.getOut().write( output.get() );
+            }
+        }
+        catch ( final Exception e )
+        {
+            try
+            {
+                pageContext.getOut().write( "errorGeneratingPwmFormID" );
+            }
+            catch ( final IOException e1 )
+            {
+                /* ignore */
+            }
+            LOGGER.error( () -> "error during PwmTextFileTag output of PwmTextFileTag: " + e.getMessage(), e );
+        }
+
+        return EVAL_PAGE;
+    }
+}

+ 3 - 0
server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java

@@ -24,6 +24,7 @@ import lombok.Value;
 import password.pwm.Permission;
 import password.pwm.config.PwmSetting;
 import password.pwm.http.PwmRequestFlag;
+import password.pwm.http.servlet.resource.TextFileResource;
 
 @Value
 class PwmIfOptions
@@ -32,4 +33,6 @@ class PwmIfOptions
     private final Permission permission;
     private final PwmSetting pwmSetting;
     private final PwmRequestFlag requestFlag;
+    private final TextFileResource textFileResource;
+
 }

+ 8 - 1
server/src/main/java/password/pwm/http/tag/conditional/PwmIfTag.java

@@ -27,6 +27,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
+import password.pwm.http.servlet.resource.TextFileResource;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.http.HttpServletRequest;
@@ -43,6 +44,7 @@ public class PwmIfTag extends BodyTagSupport
     private boolean negate;
     private PwmRequestFlag requestFlag;
     private PwmSetting setting;
+    private TextFileResource textFileResource;
 
     public void setTest( final PwmIfTest test )
     {
@@ -69,6 +71,11 @@ public class PwmIfTag extends BodyTagSupport
         this.setting = setting;
     }
 
+    public void setTextFileResource( final TextFileResource textFileResource )
+    {
+        this.textFileResource = textFileResource;
+    }
+
     @Override
     public int doStartTag( )
             throws JspException
@@ -90,7 +97,7 @@ public class PwmIfTag extends BodyTagSupport
                     {
                         try
                         {
-                            final PwmIfOptions options = new PwmIfOptions( negate, permission, setting, requestFlag );
+                            final PwmIfOptions options = new PwmIfOptions( negate, permission, setting, requestFlag, textFileResource );
                             showBody = testEnum.passed( pwmRequest, options );
                         }
                         catch ( final ChaiUnavailableException e )

+ 29 - 1
server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -36,6 +36,7 @@ import password.pwm.health.HealthService;
 import password.pwm.health.HealthStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
+import password.pwm.http.servlet.resource.TextFileResource;
 import password.pwm.user.UserInfo;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.otp.OTPUserRecord;
@@ -99,6 +100,8 @@ public enum PwmIfTest
 
     headerMenuIsVisible( new HeaderMenuIsVisibleTest() ),
 
+    textFileExists( new TextFileExists() ),
+
     requestFlag( new RequestFlagTest() ),;
 
 
@@ -467,7 +470,20 @@ public enum PwmIfTest
         @Override
         public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
         {
-            return pwmRequest.endUserFunctionalityAvailable();
+            final PwmApplicationMode mode = pwmRequest.getPwmApplication().getApplicationMode();
+            if ( mode == PwmApplicationMode.NEW )
+            {
+                return false;
+            }
+            if ( PwmConstants.TRIAL_MODE )
+            {
+                return true;
+            }
+            if ( mode == PwmApplicationMode.RUNNING )
+            {
+                return true;
+            }
+            return false;
         }
 
     }
@@ -588,4 +604,16 @@ public enum PwmIfTest
             return pwmRequest.getPwmApplication().getPwmEnvironment().getDeploymentPlatform() == deploymentPlatform;
         }
     }
+
+    private static class TextFileExists implements Test
+    {
+        @Override
+        public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options )
+                throws PwmUnrecoverableException
+        {
+            final TextFileResource textFileResource = options.getTextFileResource();
+            return TextFileResource.readTextFileResource( pwmRequest, textFileResource ).isPresent();
+        }
+    }
+
 }

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

@@ -20,50 +20,29 @@
 
 package password.pwm.http.tag.value;
 
-import com.novell.ldapchai.exception.ChaiUnavailableException;
-import password.pwm.AppProperty;
-import password.pwm.Permission;
-import password.pwm.PwmDomain;
-import password.pwm.PwmApplicationMode;
-import password.pwm.PwmConstants;
-import password.pwm.config.PwmSetting;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.IdleTimeoutCalculator;
-import password.pwm.http.PwmRequest;
-import password.pwm.http.servlet.ClientApiServlet;
-import password.pwm.i18n.Admin;
-import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
-import password.pwm.util.java.PwmTimeUtil;
-import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroRequest;
 
-import javax.servlet.jsp.JspPage;
-import javax.servlet.jsp.PageContext;
-import java.util.Locale;
 import java.util.Set;
 
 public enum PwmValue
 {
-    cspNonce( new CspNonceOutput() ),
-    homeURL( new HomeUrlOutput() ),
-    passwordFieldType( new PasswordFieldTypeOutput() ),
-    responseFieldType( new ResponseFieldTypeOutput() ),
-    customJavascript( new CustomJavascriptOutput(), Flag.DoNotEscape ),
-    currentJspFilename( new CurrentJspFilenameOutput() ),
-    instanceID( new InstanceIDOutput() ),
-    headerMenuNotice( new HeaderMenuNoticeOutput() ),
-    clientETag( new ClientETag() ),
-    localeCode( new LocaleCodeOutput() ),
-    localeDir( new LocaleDirOutput() ),
-    localeFlagFile( new LocaleFlagFileOutput() ),
-    localeName( new LocaleNameOutput() ),
-    inactiveTimeRemaining( new InactiveTimeRemainingOutput() ),
-    sessionID( new SessionIDValue() ),;
-
-    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmValueTag.class );
-
-    private final ValueOutput valueOutput;
+    cspNonce( new PwmValueHandlers.CspNonceHandlerPwm() ),
+    homeURL( new PwmValueHandlers.HomeUrlHandlerPwm() ),
+    passwordFieldType( new PwmValueHandlers.PasswordFieldTypeHandlerPwm() ),
+    responseFieldType( new PwmValueHandlers.ResponseFieldTypeHandlerPwm() ),
+    customJavascript( new PwmValueHandlers.CustomJavascriptHandlerPwm(), Flag.DoNotEscape ),
+    currentJspFilename( new PwmValueHandlers.CurrentJspFilenameHandlerPwm() ),
+    instanceID( new PwmValueHandlers.InstanceIDHandlerPwm() ),
+    headerMenuNotice( new PwmValueHandlers.HeaderMenuNoticeHandlerPwm() ),
+    clientETag( new PwmValueHandlers.ClientETag() ),
+    localeCode( new PwmValueHandlers.LocaleCodeHandlerPwm() ),
+    localeDir( new PwmValueHandlers.LocaleDirHandlerPwm() ),
+    localeFlagFile( new PwmValueHandlers.LocaleFlagFileHandlerPwm() ),
+    localeName( new PwmValueHandlers.LocaleNameHandlerPwm() ),
+    inactiveTimeRemaining( new PwmValueHandlers.InactiveTimeRemainingHandlerPwm() ),
+    sessionID( new PwmValueHandlers.SessionIDPwmValue() ),;
+
+    private final PwmValueHandler pwmValueHandler;
     private final Set<Flag> flags;
 
     enum Flag
@@ -71,15 +50,15 @@ public enum PwmValue
         DoNotEscape,
     }
 
-    PwmValue( final ValueOutput valueOutput, final Flag... flags )
+    PwmValue( final PwmValueHandler pwmValueHandler, final Flag... flags )
     {
-        this.valueOutput = valueOutput;
+        this.pwmValueHandler = pwmValueHandler;
         this.flags = CollectionUtil.enumSetFromArray( flags );
     }
 
-    public ValueOutput getValueOutput( )
+    public PwmValueHandler getValueOutput( )
     {
-        return valueOutput;
+        return pwmValueHandler;
     }
 
     public Set<Flag> getFlags( )
@@ -87,211 +66,5 @@ public enum PwmValue
         return flags;
     }
 
-    static class CspNonceOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            return pwmRequest.getCspNonce();
-        }
-    }
-
-    static class HomeUrlOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            String outputURL = pwmRequest.getDomainConfig().readSettingAsString( PwmSetting.URL_HOME );
-            if ( outputURL == null || outputURL.isEmpty() )
-            {
-                outputURL = pwmRequest.getHttpServletRequest().getContextPath();
-            }
-            else
-            {
-                try
-                {
-                    final MacroRequest macroRequest = pwmRequest.getMacroMachine();
-                    outputURL = macroRequest.expandMacros( outputURL );
-                }
-                catch ( final PwmUnrecoverableException e )
-                {
-                    LOGGER.error( pwmRequest, () -> "error expanding macros in homeURL: " + e.getMessage() );
-                }
-            }
-            return outputURL;
-        }
-    }
-
-    static class PasswordFieldTypeOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            final boolean maskPasswordFields = pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_MASK_PASSWORD_FIELDS );
-            return maskPasswordFields ? "password" : "text";
-        }
-    }
-
-    static class ResponseFieldTypeOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            final boolean maskPasswordFields = pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_MASK_RESPONSE_FIELDS );
-            return maskPasswordFields ? "password" : "text";
-        }
-    }
-
-    static class CustomJavascriptOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            final String customScript = pwmRequest.getDomainConfig().readSettingAsString(
-                    PwmSetting.DISPLAY_CUSTOM_JAVASCRIPT );
-            if ( customScript != null && !customScript.isEmpty() )
-            {
-                try
-                {
-                    final MacroRequest macroRequest = pwmRequest.getMacroMachine();
-                    return macroRequest.expandMacros( customScript );
-                }
-                catch ( final Exception e )
-                {
-                    LOGGER.error( pwmRequest, () -> "error while expanding customJavascript macros: " + e.getMessage() );
-                    return customScript;
-                }
-            }
-            return "";
-        }
-    }
-
-    static class CurrentJspFilenameOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            final JspPage jspPage = ( JspPage ) pageContext.getPage();
-            if ( jspPage != null )
-            {
-                String name = jspPage.getClass().getSimpleName();
-                name = name.replaceAll( "_002d", "-" );
-                return name.replaceAll( "_", "." );
-            }
-            return "";
-        }
-    }
-
-    static class InstanceIDOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            return pwmRequest.getPwmApplication().getInstanceID();
-
-        }
-    }
-
-    static class HeaderMenuNoticeOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws ChaiUnavailableException, PwmUnrecoverableException
-        {
-            final String[] fieldNames = new String[]
-                    {
-                            PwmConstants.PWM_APP_NAME,
-                    };
-
-            if ( PwmConstants.TRIAL_MODE )
-            {
-                return LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_TrialMode", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
-            }
-            else if ( pwmRequest.getPwmDomain().getApplicationMode() == PwmApplicationMode.CONFIGURATION )
-            {
-                String output = "";
-                if ( Boolean.parseBoolean( pwmRequest.getDomainConfig().readAppProperty( AppProperty.CLIENT_JSP_SHOW_ICONS ) ) )
-                {
-                    output += "<span id=\"icon-configModeHelp\" class=\"btn-icon pwm-icon pwm-icon-question-circle\"></span>";
-                }
-                return output + LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_ConfigModeActive", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
-            }
-            else if ( pwmRequest.checkPermission( Permission.PWMADMIN ) )
-            {
-                return LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_AdminUser", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
-            }
-
-            return "";
-        }
-    }
-
-    static class ClientETag implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
-                throws PwmUnrecoverableException
-        {
-            return ClientApiServlet.makeClientEtag( pwmRequest );
-        }
-    }
-
-    static class LocaleCodeOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
-        {
-            return pwmRequest.getLocale().toLanguageTag();
-        }
-    }
-
-    static class LocaleDirOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
-        {
-            final Locale locale = pwmRequest.getLocale();
-            final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-            final LocaleHelper.TextDirection textDirection = LocaleHelper.textDirectionForLocale( pwmDomain, locale );
-            return textDirection.name();
-        }
-    }
-
-    static class LocaleNameOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
-        {
-            final Locale locale = pwmRequest.getLocale();
-            return locale.getDisplayName( locale );
-        }
-    }
-
-    static class LocaleFlagFileOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
-        {
-            final String flagFileName = pwmRequest.getAppConfig().getKnownLocaleFlagMap().get( pwmRequest.getLocale() );
-            return flagFileName == null ? "" : flagFileName;
-        }
-    }
-
-    static class InactiveTimeRemainingOutput implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
-                throws PwmUnrecoverableException
-        {
-            return PwmTimeUtil.asLongString( IdleTimeoutCalculator.idleTimeoutForRequest( pwmRequest ) );
-        }
-    }
 
-    static class SessionIDValue implements ValueOutput
-    {
-        @Override
-        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
-                throws PwmUnrecoverableException
-        {
-            return pwmRequest.getPwmSession().getSessionStateBean().getSessionID();
-        }
-    }
 }

+ 2 - 3
server/src/main/java/password/pwm/http/tag/value/ValueOutput.java → server/src/main/java/password/pwm/http/tag/value/PwmValueHandler.java

@@ -20,17 +20,16 @@
 
 package password.pwm.http.tag.value;
 
-import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 
 import javax.servlet.jsp.PageContext;
 
-public interface ValueOutput
+public interface PwmValueHandler
 {
     String valueOutput(
             PwmRequest pwmRequest,
             PageContext pageContext
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException;
+            throws PwmUnrecoverableException;
 }

+ 256 - 0
server/src/main/java/password/pwm/http/tag/value/PwmValueHandlers.java

@@ -0,0 +1,256 @@
+/*
+ * 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.http.tag.value;
+
+import password.pwm.AppProperty;
+import password.pwm.Permission;
+import password.pwm.PwmApplicationMode;
+import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.IdleTimeoutCalculator;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.servlet.ClientApiServlet;
+import password.pwm.i18n.Admin;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.PwmTimeUtil;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroRequest;
+
+import javax.servlet.jsp.JspPage;
+import javax.servlet.jsp.PageContext;
+import java.util.Locale;
+
+public class PwmValueHandlers
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmValueHandlers.class );
+
+
+
+    static class CspNonceHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            return pwmRequest.getCspNonce();
+        }
+    }
+
+    static class HomeUrlHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            String outputURL = pwmRequest.getDomainConfig().readSettingAsString( PwmSetting.URL_HOME );
+            if ( outputURL == null || outputURL.isEmpty() )
+            {
+                outputURL = pwmRequest.getHttpServletRequest().getContextPath();
+            }
+            else
+            {
+                try
+                {
+                    final MacroRequest macroRequest = pwmRequest.getMacroMachine();
+                    outputURL = macroRequest.expandMacros( outputURL );
+                }
+                catch ( final PwmUnrecoverableException e )
+                {
+                    LOGGER.error( pwmRequest, () -> "error expanding macros in homeURL: " + e.getMessage() );
+                }
+            }
+            return outputURL;
+        }
+    }
+
+    static class PasswordFieldTypeHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            final boolean maskPasswordFields = pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_MASK_PASSWORD_FIELDS );
+            return maskPasswordFields ? "password" : "text";
+        }
+    }
+
+    static class ResponseFieldTypeHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            final boolean maskPasswordFields = pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_MASK_RESPONSE_FIELDS );
+            return maskPasswordFields ? "password" : "text";
+        }
+    }
+
+    static class CustomJavascriptHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            final String customScript = pwmRequest.getDomainConfig().readSettingAsString(
+                    PwmSetting.DISPLAY_CUSTOM_JAVASCRIPT );
+            if ( customScript != null && !customScript.isEmpty() )
+            {
+                try
+                {
+                    final MacroRequest macroRequest = pwmRequest.getMacroMachine();
+                    return macroRequest.expandMacros( customScript );
+                }
+                catch ( final Exception e )
+                {
+                    LOGGER.error( pwmRequest, () -> "error while expanding customJavascript macros: " + e.getMessage() );
+                    return customScript;
+                }
+            }
+            return "";
+        }
+    }
+
+    static class CurrentJspFilenameHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            final JspPage jspPage = ( JspPage ) pageContext.getPage();
+            if ( jspPage != null )
+            {
+                String name = jspPage.getClass().getSimpleName();
+                name = name.replaceAll( "_002d", "-" );
+                return name.replaceAll( "_", "." );
+            }
+            return "";
+        }
+    }
+
+    static class InstanceIDHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            return pwmRequest.getPwmApplication().getInstanceID();
+
+        }
+    }
+
+    static class HeaderMenuNoticeHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext ) throws PwmUnrecoverableException
+        {
+            final String[] fieldNames = new String[]
+                    {
+                            PwmConstants.PWM_APP_NAME,
+                    };
+
+            if ( PwmConstants.TRIAL_MODE )
+            {
+                return LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_TrialMode", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
+            }
+            else if ( pwmRequest.getPwmDomain().getApplicationMode() == PwmApplicationMode.CONFIGURATION )
+            {
+                String output = "";
+                if ( Boolean.parseBoolean( pwmRequest.getDomainConfig().readAppProperty( AppProperty.CLIENT_JSP_SHOW_ICONS ) ) )
+                {
+                    output += "<span id=\"icon-configModeHelp\" class=\"btn-icon pwm-icon pwm-icon-question-circle\"></span>";
+                }
+                return output + LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_ConfigModeActive", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
+            }
+            else if ( pwmRequest.checkPermission( Permission.PWMADMIN ) )
+            {
+                return LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_AdminUser", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
+            }
+
+            return "";
+        }
+    }
+
+    static class ClientETag implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
+                throws PwmUnrecoverableException
+        {
+            return ClientApiServlet.makeClientEtag( pwmRequest );
+        }
+    }
+
+    static class LocaleCodeHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
+        {
+            return pwmRequest.getLocale().toLanguageTag();
+        }
+    }
+
+    static class LocaleDirHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
+        {
+            final Locale locale = pwmRequest.getLocale();
+            final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
+            final LocaleHelper.TextDirection textDirection = LocaleHelper.textDirectionForLocale( pwmDomain, locale );
+            return textDirection.name();
+        }
+    }
+
+    static class LocaleNameHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
+        {
+            final Locale locale = pwmRequest.getLocale();
+            return locale.getDisplayName( locale );
+        }
+    }
+
+    static class LocaleFlagFileHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
+        {
+            final String flagFileName = pwmRequest.getAppConfig().getKnownLocaleFlagMap().get( pwmRequest.getLocale() );
+            return flagFileName == null ? "" : flagFileName;
+        }
+    }
+
+    static class InactiveTimeRemainingHandlerPwm implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
+                throws PwmUnrecoverableException
+        {
+            return PwmTimeUtil.asLongString( IdleTimeoutCalculator.idleTimeoutForRequest( pwmRequest ) );
+        }
+    }
+
+    static class SessionIDPwmValue implements PwmValueHandler
+    {
+        @Override
+        public String valueOutput( final PwmRequest pwmRequest, final PageContext pageContext )
+                throws PwmUnrecoverableException
+        {
+            return pwmRequest.getPwmSession().getSessionStateBean().getSessionID();
+        }
+    }
+}

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

@@ -130,7 +130,7 @@ public class LdapBrowser
 
         result.dn( dn );
         result.profileID( profileID );
-        final DomainConfig domainConfig = new AppConfig( storedConfiguration ).getDomainConfigs().get( domainID );
+        final DomainConfig domainConfig = AppConfig.forStoredConfig( storedConfiguration ).getDomainConfigs().get( domainID );
 
         if ( domainConfig.getLdapProfiles().size() > 1 )
         {
@@ -193,7 +193,7 @@ public class LdapBrowser
     {
         if ( !providerCache.containsKey( profile ) )
         {
-            final DomainConfig domainConfig = new AppConfig( storedConfiguration ).getDomainConfigs().get( domainID );
+            final DomainConfig domainConfig = AppConfig.forStoredConfig( storedConfiguration ).getDomainConfigs().get( domainID );
             final LdapProfile ldapProfile = domainConfig.getLdapProfiles().get( profile );
             final ChaiProvider chaiProvider = LdapOperationsHelper.openProxyChaiProvider( chaiProviderFactory, sessionLabel, ldapProfile, domainConfig, null );
             providerCache.put( profile, chaiProvider );
@@ -203,7 +203,7 @@ public class LdapBrowser
 
     private int getMaxSizeLimit( )
     {
-        final AppConfig appConfig = new AppConfig( storedConfiguration );
+        final AppConfig appConfig = AppConfig.forStoredConfig( storedConfiguration );
         return Integer.parseInt( appConfig.readAppProperty( AppProperty.LDAP_BROWSER_MAX_ENTRIES ) );
     }
 

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

@@ -60,14 +60,12 @@ import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReentrantLock;
 
 public class LdapDomainService extends AbstractPwmService implements PwmService
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( LdapDomainService.class );
 
     private final Map<String, ErrorInformation> lastLdapErrors = new ConcurrentHashMap<>();
-    private final ReentrantLock reentrantLock = new ReentrantLock();
     private final ConditionalTaskExecutor debugLogger = ConditionalTaskExecutor.forPeriodicTask(
             this::conditionallyLogDebugInfo,
             TimeDuration.MINUTE.asDuration() );
@@ -82,7 +80,7 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
     public static long totalLdapConnectionCount( final PwmApplication pwmApplication )
     {
         return pwmApplication.domains().values().stream()
-                .map( PwmDomain::getLdapConnectionService )
+                .map( PwmDomain::getLdapService )
                 .map( LdapDomainService::connectionCount )
                 .map( Long::valueOf )
                 .reduce( 0L, Long::sum );

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

@@ -52,7 +52,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.data.ImmutableByteArray;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.stats.EpsStatistic;
@@ -154,7 +154,7 @@ public class LdapOperationsHelper
             throws PwmUnrecoverableException
     {
         return openProxyChaiProvider(
-                pwmDomain.getLdapConnectionService().getChaiProviderFactory(),
+                pwmDomain.getLdapService().getChaiProviderFactory(),
                 sessionLabel,
                 ldapProfile,
                 config,
@@ -473,7 +473,7 @@ public class LdapOperationsHelper
 
             if ( guidMode == AbstractProfile.GuidMode.DN )
             {
-                return userIdentity.toDelimitedKey();
+                return userIdentity.getUserDN();
             }
 
             if ( guidMode == AbstractProfile.GuidMode.VENDORGUID )
@@ -568,8 +568,8 @@ public class LdapOperationsHelper
                                 .filter( "(" + guidAttributeName + "=" + guidValue + ")" )
                                 .build();
 
-                        final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
-                        final UserIdentity result = userSearchEngine.performSingleUserSearch( searchConfiguration, sessionLabel );
+                        final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
+                        final UserIdentity result = userSearchService.performSingleUserSearch( searchConfiguration, sessionLabel );
                         exists = result != null;
                     }
                     catch ( final PwmOperationalException e )
@@ -660,7 +660,7 @@ public class LdapOperationsHelper
             throws ChaiUnavailableException, PwmUnrecoverableException
     {
         final ChaiProvider chaiProvider = createChaiProvider(
-                pwmDomain.getLdapConnectionService().getChaiProviderFactory(),
+                pwmDomain.getLdapService().getChaiProviderFactory(),
                 sessionLabel,
                 ldapProfile,
                 config,
@@ -702,7 +702,7 @@ public class LdapOperationsHelper
     {
         final ChaiConfiguration chaiConfig = createChaiConfiguration( config, ldapProfile, ldapURLs, userDN, userPassword );
         LOGGER.trace( sessionLabel, () -> "creating new ldap connection using config: " + chaiConfig.toString() );
-        return pwmDomain.getLdapConnectionService().getChaiProviderFactory().newProvider( chaiConfig );
+        return pwmDomain.getLdapService().getChaiProviderFactory().newProvider( chaiConfig );
     }
 
     public static ChaiConfiguration createChaiConfiguration(

+ 7 - 7
server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java

@@ -44,7 +44,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.intruder.IntruderDomainService;
 import password.pwm.svc.intruder.IntruderRecordType;
 import password.pwm.svc.intruder.IntruderServiceClient;
@@ -93,8 +93,8 @@ public class SessionAuthenticator
         UserIdentity userIdentity = null;
         try
         {
-            final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
-            userIdentity = userSearchEngine.resolveUsername( username, context, ldapProfile, sessionLabel );
+            final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
+            userIdentity = userSearchService.resolveUsername( username, context, ldapProfile, sessionLabel );
 
             final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest(
                     pwmDomain,
@@ -196,8 +196,8 @@ public class SessionAuthenticator
         UserIdentity userIdentity = null;
         try
         {
-            final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
-            userIdentity = userSearchEngine.resolveUsername( username, null, null, sessionLabel );
+            final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
+            userIdentity = userSearchService.resolveUsername( username, null, null, sessionLabel );
 
             final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest(
                     pwmDomain,
@@ -341,7 +341,7 @@ public class SessionAuthenticator
         final IntruderDomainService intruderManager = pwmDomain.getIntruderService();
         if ( intruderManager != null )
         {
-            IntruderServiceClient.markAddressAndSession( pwmRequest.getPwmDomain(), pwmRequest.getPwmSession() );
+            IntruderServiceClient.markAddressAndSession( pwmRequest );
 
             if ( username != null )
             {
@@ -371,7 +371,7 @@ public class SessionAuthenticator
         loginInfoBean.setUserIdentity( userIdentity );
 
         //update the session connection
-        pwmSession.updateLdapAuthentication( pwmRequest.getPwmApplication(), userIdentity, authenticationResult );
+        pwmSession.updateLdapAuthentication( sessionLabel, pwmRequest.getPwmApplication(), userIdentity, authenticationResult );
 
         // update the actor user info bean
         {

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

@@ -32,7 +32,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequestContext;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -147,7 +147,7 @@ public class UserPermissionUtility
         final List<UserPermission> sortedPermissions = new ArrayList<>( userPermissions );
         Collections.sort( sortedPermissions );
 
-        final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+        final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
         final List<UserIdentity> resultSet = new ArrayList<>();
 
         for ( final UserPermission userPermission : sortedPermissions )
@@ -162,7 +162,7 @@ public class UserPermissionUtility
 
                 try
                 {
-                    final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
+                    final Map<UserIdentity, Map<String, String>> results = userSearchService.performMultiUserSearch(
                             searchConfiguration,
                             ( maxResultSize ) - resultSet.size(),
                             Collections.emptyList(),

+ 6 - 6
server/src/main/java/password/pwm/ldap/search/UserSearchJob.java

@@ -46,15 +46,15 @@ class UserSearchJob implements Callable<Map<UserIdentity, Map<String, String>>>
 {
     private final PwmDomain pwmDomain;
     private final UserSearchJobParameters userSearchJobParameters;
-    private final UserSearchEngine userSearchEngine;
+    private final UserSearchService userSearchService;
     private final FutureTask<Map<UserIdentity, Map<String, String>>> futureTask;
     private final Instant createTime = Instant.now();
 
-    UserSearchJob( final PwmDomain pwmDomain, final UserSearchEngine userSearchEngine, final UserSearchJobParameters userSearchJobParameters )
+    UserSearchJob( final PwmDomain pwmDomain, final UserSearchService userSearchService, final UserSearchJobParameters userSearchJobParameters )
     {
         this.pwmDomain = pwmDomain;
         this.userSearchJobParameters = userSearchJobParameters;
-        this.userSearchEngine = userSearchEngine;
+        this.userSearchService = userSearchService;
         this.futureTask = new FutureTask<>( this );
     }
 
@@ -82,7 +82,7 @@ class UserSearchJob implements Callable<Map<UserIdentity, Map<String, String>>>
             debugInfo = "[" + StringUtil.mapToString( props ) + "]";
         }
 
-        userSearchEngine.log( PwmLogLevel.TRACE, userSearchJobParameters.getSessionLabel(), userSearchJobParameters.getSearchID(), userSearchJobParameters.getJobId(),
+        userSearchService.log( PwmLogLevel.TRACE, userSearchJobParameters.getSessionLabel(), userSearchJobParameters.getSearchID(), userSearchJobParameters.getJobId(),
                 "performing ldap search for user, thread=" + Thread.currentThread().getId()
                         + ", timeout=" + userSearchJobParameters.getTimeoutMs() + "ms, "
                         + debugInfo );
@@ -117,12 +117,12 @@ class UserSearchJob implements Callable<Map<UserIdentity, Map<String, String>>>
 
         if ( results.isEmpty() )
         {
-            userSearchEngine.log( PwmLogLevel.TRACE, userSearchJobParameters.getSessionLabel(), userSearchJobParameters.getSearchID(), userSearchJobParameters.getJobId(),
+            userSearchService.log( PwmLogLevel.TRACE, userSearchJobParameters.getSessionLabel(), userSearchJobParameters.getSearchID(), userSearchJobParameters.getJobId(),
                     "no matches from search (" + searchDuration.asCompactString() + "); " + debugInfo );
             return Collections.emptyMap();
         }
 
-        userSearchEngine.log( PwmLogLevel.TRACE, userSearchJobParameters.getSessionLabel(), userSearchJobParameters.getSearchID(), userSearchJobParameters.getJobId(),
+        userSearchService.log( PwmLogLevel.TRACE, userSearchJobParameters.getSessionLabel(), userSearchJobParameters.getSearchID(), userSearchJobParameters.getJobId(),
                 "found " + results.size() + " results in " + searchDuration.asCompactString() + "; " + debugInfo );
 
         final Map<UserIdentity, Map<String, String>> returnMap = new LinkedHashMap<>( results.size() );

+ 44 - 27
server/src/main/java/password/pwm/ldap/search/UserSearchResults.java

@@ -20,34 +20,40 @@
 
 package password.pwm.ldap.search;
 
-import password.pwm.PwmDomain;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 
 public class UserSearchResults implements Serializable
 {
+    public static final String JSON_KEY_USER_KEY = "userKey";
+    public static final String JSON_KEY_ID = "id";
+
     private final Map<String, String> headerAttributeMap;
     private final Map<UserIdentity, Map<String, String>> results;
     private final boolean sizeExceeded;
 
-    public UserSearchResults( final Map<String, String> headerAttributeMap, final Map<UserIdentity, Map<String, String>> results, final boolean sizeExceeded )
+    public UserSearchResults(
+            final Map<String, String> headerAttributeMap,
+            final Map<UserIdentity, Map<String, String>> results,
+            final boolean sizeExceeded
+    )
     {
-        this.headerAttributeMap = headerAttributeMap;
-        this.results = Collections.unmodifiableMap( defaultSort( results, headerAttributeMap ) );
+        this.headerAttributeMap = headerAttributeMap == null ? Collections.emptyMap() : Map.copyOf( headerAttributeMap );
+        this.results = Map.copyOf( defaultSort( results, headerAttributeMap ) );
         this.sizeExceeded = sizeExceeded;
-
     }
 
     private static Map<UserIdentity, Map<String, String>> defaultSort(
@@ -69,7 +75,7 @@ public class UserSearchResults implements Serializable
                 .sorted( comparator )
                 .collect( CollectionUtil.collectorToLinkedMap(
                         Function.identity(),
-                        results::get
+                        userIdentity -> CollectionUtil.stripNulls( results.get( userIdentity ) )
                 ) ) );
     }
 
@@ -89,31 +95,42 @@ public class UserSearchResults implements Serializable
     }
 
     public List<Map<String, Object>> resultsAsJsonOutput(
-            final PwmDomain pwmDomain,
+            final Function<UserIdentity, String> identityEncoder,
             final UserIdentity ignoreUser
     )
-            throws PwmUnrecoverableException
     {
-        final List<Map<String, Object>> outputList = new ArrayList<>( this.getResults().size() );
-        int idCounter = 0;
-        for ( final UserIdentity userIdentity : this.getResults().keySet() )
+        final AtomicInteger idCounter = new AtomicInteger();
+
+        final Function<UserIdentity, Map<String, Object>> makeRowMap = userIdentity ->
         {
-            if ( ignoreUser == null || !ignoreUser.equals( userIdentity ) )
-            {
-                final Map<String, Object> rowMap = new LinkedHashMap<>( this.getHeaderAttributeMap().size() );
-                for ( final String attribute : this.getHeaderAttributeMap().keySet() )
-                {
-                    rowMap.put( attribute, this.getResults().get( userIdentity ).get( attribute ) );
-                }
-                rowMap.put( "userKey", userIdentity.toObfuscatedKey( pwmDomain.getPwmApplication() ) );
-                rowMap.put( "id", idCounter );
-                outputList.add( rowMap );
-                idCounter++;
-            }
+            final Map<String, Object> rowMap = headerAttributeMap.keySet().stream()
+                    .collect( CollectionUtil.collectorToLinkedMap(
+                            Function.identity(),
+                            attribute -> attributeValue( userIdentity, attribute ) ) );
+
+            rowMap.put( JSON_KEY_USER_KEY, identityEncoder.apply( userIdentity ) );
+            rowMap.put( JSON_KEY_ID, idCounter.getAndIncrement() );
+            return Map.copyOf( rowMap );
+        };
+
+        return this.getResults().keySet().stream()
+                .filter( Objects::nonNull )
+                .filter( userIdentity -> ignoreUser == null || !ignoreUser.equals( userIdentity ) )
+                .map( makeRowMap )
+                .collect( Collectors.toUnmodifiableList() );
+    }
+
+    private String attributeValue( final UserIdentity userIdentity, final String attributeName )
+    {
+        if ( userIdentity == null || StringUtil.isEmpty( attributeName ) )
+        {
+            return "";
         }
-        return outputList;
+
+        return results.getOrDefault( userIdentity, Collections.emptyMap() ).getOrDefault( attributeName, "" );
     }
 
+
     public static Map<String, String> fromFormConfiguration( final List<FormConfiguration> formItems, final Locale locale )
     {
         return Collections.unmodifiableMap( formItems.stream().collect( CollectionUtil.collectorToLinkedMap(

+ 6 - 44
server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java → server/src/main/java/password/pwm/ldap/search/UserSearchService.java

@@ -20,9 +20,6 @@
 
 package password.pwm.ldap.search;
 
-import com.novell.ldapchai.ChaiUser;
-import com.novell.ldapchai.exception.ChaiOperationException;
-import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -75,9 +72,9 @@ import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ThreadPoolExecutor;
 
 
-public class UserSearchEngine extends AbstractPwmService implements PwmService
+public class UserSearchService extends AbstractPwmService implements PwmService
 {
-    private static final PwmLogger LOGGER = PwmLogger.forClass( UserSearchEngine.class );
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserSearchService.class );
 
     private final StatisticCounterBundle<SearchStatistic> counters = new StatisticCounterBundle<>( SearchStatistic.class );
     private final AtomicLoopIntIncrementer searchIdCounter = new AtomicLoopIntIncrementer();
@@ -101,7 +98,7 @@ public class UserSearchEngine extends AbstractPwmService implements PwmService
             TimeDuration.of( 1, TimeDuration.Unit.MINUTES ).asDuration()
     );
 
-    public UserSearchEngine( )
+    public UserSearchService( )
     {
     }
 
@@ -152,41 +149,6 @@ public class UserSearchEngine extends AbstractPwmService implements PwmService
     )
             throws PwmUnrecoverableException, PwmOperationalException
     {
-        //check if username is a key
-        {
-            UserIdentity inputIdentity = null;
-            try
-            {
-                inputIdentity = UserIdentity.fromKey( sessionLabel, username, pwmDomain.getPwmApplication() );
-            }
-            catch ( final PwmException e )
-            {
-                /* input is not a userIdentity */
-            }
-
-            if ( inputIdentity != null )
-            {
-                try
-                {
-                    final ChaiUser theUser = pwmDomain.getProxiedChaiUser( sessionLabel, inputIdentity );
-                    if ( theUser.exists() )
-                    {
-                        final String canonicalDN;
-                        canonicalDN = theUser.readCanonicalDN();
-                        return UserIdentity.create( canonicalDN, inputIdentity.getLdapProfileID(), pwmDomain.getDomainID() );
-                    }
-                }
-                catch ( final ChaiOperationException e )
-                {
-                    throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_CANT_MATCH_USER, e.getMessage() ) );
-                }
-                catch ( final ChaiUnavailableException e )
-                {
-                    throw PwmUnrecoverableException.fromChaiException( e );
-                }
-            }
-        }
-
         try
         {
             //see if we need to do a contextless search.
@@ -343,7 +305,7 @@ public class UserSearchEngine extends AbstractPwmService implements PwmService
         for ( final LdapProfile ldapProfile : ldapProfiles )
         {
             boolean skipProfile = false;
-            final Instant lastLdapFailure = pwmDomain.getLdapConnectionService().getLastLdapFailureTime( ldapProfile );
+            final Instant lastLdapFailure = pwmDomain.getLdapService().getLastLdapFailureTime( ldapProfile );
 
             if ( ldapProfiles.size() > 1 && lastLdapFailure != null && TimeDuration.fromCurrent( lastLdapFailure ).isShorterThan( profileRetryDelayMS ) )
             {
@@ -369,7 +331,7 @@ public class UserSearchEngine extends AbstractPwmService implements PwmService
                 {
                     if ( e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE )
                     {
-                        pwmDomain.getLdapConnectionService().setLastLdapFailure( ldapProfile, e.getErrorInformation() );
+                        pwmDomain.getLdapService().setLastLdapFailure( ldapProfile, e.getErrorInformation() );
                         if ( ignoreUnreachableProfiles )
                         {
                             errors.add( e.getErrorInformation().getDetailedErrorMsg() );
@@ -814,7 +776,7 @@ public class UserSearchEngine extends AbstractPwmService implements PwmService
 
             LOGGER.trace( getSessionLabel(), () -> "initialized with threads min=" + minThreads + " max=" + threads );
 
-            return PwmScheduler.makeMultiThreadExecutor( threads, getPwmApplication(), getSessionLabel(), UserSearchEngine.class );
+            return PwmScheduler.makeMultiThreadExecutor( threads, getPwmApplication(), getSessionLabel(), UserSearchService.class );
         }
         return null;
     }

+ 2 - 1
server/src/main/java/password/pwm/svc/PwmServiceEnum.java

@@ -23,6 +23,7 @@ package password.pwm.svc;
 import password.pwm.config.PwmSettingScope;
 import password.pwm.health.HealthService;
 import password.pwm.ldap.LdapDomainService;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.email.EmailService;
 import password.pwm.svc.intruder.IntruderDomainService;
 import password.pwm.svc.intruder.IntruderSystemService;
@@ -70,7 +71,7 @@ public enum PwmServiceEnum
     CrService( password.pwm.svc.cr.CrService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
     OtpService( password.pwm.svc.otp.OtpService.class, PwmSettingScope.DOMAIN ),
     IntruderDomainService( IntruderDomainService.class, PwmSettingScope.DOMAIN ),
-    UserSearchEngine( password.pwm.ldap.search.UserSearchEngine.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
+    UserSearchEngine( UserSearchService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
     TokenService( password.pwm.svc.token.TokenService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
     UserHistoryService( password.pwm.svc.userhistory.UserHistoryService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
     PeopleSearchService( password.pwm.http.servlet.peoplesearch.PeopleSearchService.class, PwmSettingScope.DOMAIN ),

+ 1 - 1
server/src/main/java/password/pwm/svc/cr/NMASCrOperator.java

@@ -496,7 +496,7 @@ public class NMASCrOperator implements CrOperator
 
         private LDAPConnection makeLdapConnection( ) throws Exception
         {
-            final ChaiProviderFactory chaiProviderFactory = pwmDomain.getLdapConnectionService().getChaiProviderFactory();
+            final ChaiProviderFactory chaiProviderFactory = pwmDomain.getLdapService().getChaiProviderFactory();
             final ChaiProvider chaiProvider = chaiProviderFactory.newProvider( chaiConfiguration );
             final ChaiUser theUser = chaiProvider.getEntryFactory().newChaiUser( userIdentity.getUserDN() );
             try

+ 52 - 29
server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java

@@ -83,7 +83,7 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
     private IntruderSettings intruderSettings;
     private ServiceInfoBean serviceInfo = ServiceInfoBean.builder().build();
 
-    public IntruderDomainService( )
+    public IntruderDomainService()
     {
         EnumSet.allOf( IntruderRecordType.class ).forEach( recordType -> recordManagers.put( recordType, new StubRecordManager() ) );
     }
@@ -170,7 +170,8 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
         return dataStore;
     }
 
-    private void initializeRecordManagers() throws PwmUnrecoverableException
+    private void initializeRecordManagers()
+            throws PwmUnrecoverableException
     {
         this.recordManagers.clear();
         final IntruderRecordStore recordStore = pwmDomain.getPwmApplication().getIntruderSystemService().getRecordStore();
@@ -193,13 +194,13 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
 
 
     @Override
-    public void shutdownImpl( )
+    public void shutdownImpl()
     {
         setStatus( STATUS.CLOSED );
     }
 
     @Override
-    public List<HealthRecord> serviceHealthCheck( )
+    public List<HealthRecord> serviceHealthCheck()
     {
         return Collections.emptyList();
     }
@@ -292,13 +293,17 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
 
         if ( recordType == IntruderRecordType.USER_ID )
         {
-            final UserIdentity userIdentity = UserIdentity.fromDelimitedKey( sessionLabel, subject );
-            final UserAuditRecord auditRecord = AuditRecordFactory.make( sessionLabel, pwmDomain ).createUserAuditRecord(
-                    AuditEvent.INTRUDER_USER_ATTEMPT,
-                    userIdentity,
-                    sessionLabel
-            );
-            AuditServiceClient.submit( pwmDomain.getPwmApplication(), sessionLabel, auditRecord );
+
+            final Optional<UserIdentity> userIdentity = subjectToIdentity( sessionLabel, subject );
+            if ( userIdentity.isPresent() )
+            {
+                final UserAuditRecord auditRecord = AuditRecordFactory.make( sessionLabel, pwmDomain ).createUserAuditRecord(
+                        AuditEvent.INTRUDER_USER_ATTEMPT,
+                        userIdentity.get(),
+                        sessionLabel
+                );
+                AuditServiceClient.submit( pwmDomain.getPwmApplication(), sessionLabel, auditRecord );
+            }
         }
         else
         {
@@ -323,14 +328,17 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
             {
                 if ( recordType == IntruderRecordType.USER_ID )
                 {
-                    final UserIdentity userIdentity = UserIdentity.fromDelimitedKey( sessionLabel, subject );
-                    final UserAuditRecord auditRecord = AuditRecordFactory.make( sessionLabel, pwmDomain ).createUserAuditRecord(
-                            AuditEvent.INTRUDER_USER_LOCK,
-                            userIdentity,
-                            sessionLabel
-                    );
-                    AuditServiceClient.submit( pwmDomain.getPwmApplication(), sessionLabel, auditRecord );
-                    manager.readIntruderRecord( subject ).ifPresent( record -> sendAlert( record, sessionLabel ) );
+                    final Optional<UserIdentity> userIdentity = subjectToIdentity( sessionLabel, subject );
+                    if ( userIdentity.isPresent() )
+                    {
+                        final UserAuditRecord auditRecord = AuditRecordFactory.make( sessionLabel, pwmDomain ).createUserAuditRecord(
+                                AuditEvent.INTRUDER_USER_LOCK,
+                                userIdentity.get(),
+                                sessionLabel
+                        );
+                        AuditServiceClient.submit( pwmDomain.getPwmApplication(), sessionLabel, auditRecord );
+                        manager.readIntruderRecord( subject ).ifPresent( record -> sendAlert( record, sessionLabel ) );
+                    }
                 }
                 else
                 {
@@ -387,15 +395,8 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
 
         if ( intruderRecord.getType() == IntruderRecordType.USER_ID )
         {
-            try
-            {
-                final UserIdentity identity = UserIdentity.fromDelimitedKey( sessionLabel, intruderRecord.getSubject() );
-                sendIntruderNoticeEmail( pwmDomain, sessionLabel, identity );
-            }
-            catch ( final PwmUnrecoverableException e )
-            {
-                LOGGER.error( sessionLabel, () -> "unable to send intruder mail, can't read userDN/ldapProfile from stored record: " + e.getMessage() );
-            }
+            final Optional<UserIdentity> userIdentity = subjectToIdentity( sessionLabel, intruderRecord.getSubject() );
+            userIdentity.ifPresent( identity -> sendIntruderNoticeEmail( pwmDomain, sessionLabel, identity ) );
         }
     }
 
@@ -437,7 +438,7 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
     }
 
     @Override
-    public ServiceInfoBean serviceInfo( )
+    public ServiceInfoBean serviceInfo()
     {
         return serviceInfo;
     }
@@ -458,4 +459,26 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
 
         return intruderRecord.get().getAttemptCount();
     }
+
+    static String identityToSubject( final UserIdentity userIdentity )
+    {
+        return JsonFactory.get().serialize( userIdentity );
+    }
+
+    static Optional<UserIdentity> subjectToIdentity( final SessionLabel sessionLabel, final String subject )
+    {
+        if ( !StringUtil.isEmpty( subject ) )
+        {
+            try
+            {
+                return Optional.of( JsonFactory.get().deserialize( subject, UserIdentity.class ) );
+            }
+            catch ( final Exception e )
+            {
+                LOGGER.error( sessionLabel, () -> "can't read userDN/ldapProfile from stored record: " + e.getMessage() );
+            }
+        }
+
+        return Optional.empty();
+    }
 }

+ 12 - 9
server/src/main/java/password/pwm/svc/intruder/IntruderServiceClient.java

@@ -34,6 +34,8 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 
 public class IntruderServiceClient
 {
@@ -58,16 +60,18 @@ public class IntruderServiceClient
         }
     }
 
-    public static void markAddressAndSession( final PwmDomain pwmDomain, final PwmSession pwmSession )
+    public static void markAddressAndSession( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        final IntruderDomainService intruderService = pwmDomain.getIntruderService();
+        Objects.requireNonNull( pwmRequest );
 
-        if ( pwmSession != null )
+        pwmRequest.getPwmSession().getSessionStateBean().incrementIntruderAttempts();
+
+        final Optional<String> srcAddress = pwmRequest.getSrcAddress();
+        if ( srcAddress.isPresent() )
         {
-            final String subject = pwmSession.getSessionStateBean().getSrcAddress();
-            pwmSession.getSessionStateBean().incrementIntruderAttempts();
-            intruderService.mark( IntruderRecordType.ADDRESS, subject, pwmSession.getLabel() );
+            final IntruderDomainService intruderService = pwmRequest.getPwmDomain().getIntruderService();
+            intruderService.mark( IntruderRecordType.ADDRESS, srcAddress.get(), pwmRequest.getLabel() );
         }
     }
 
@@ -110,8 +114,7 @@ public class IntruderServiceClient
 
         if ( userIdentity != null )
         {
-            final String subject = userIdentity.toDelimitedKey();
-            intruderService.mark( IntruderRecordType.USER_ID, subject, sessionLabel );
+            intruderService.mark( IntruderRecordType.USER_ID, IntruderDomainService.identityToSubject( userIdentity ), sessionLabel );
         }
     }
 
@@ -122,7 +125,7 @@ public class IntruderServiceClient
 
         if ( userIdentity != null )
         {
-            final String subject = userIdentity.toDelimitedKey();
+            final String subject = IntruderDomainService.identityToSubject( userIdentity );
             intruderService.clear( IntruderRecordType.USER_ID, subject );
         }
     }

+ 1 - 8
server/src/main/java/password/pwm/svc/report/ReportRecordLocalDBStorageService.java

@@ -121,14 +121,7 @@ public class ReportRecordLocalDBStorageService extends AbstractPwmService implem
         public UserIdentity next( )
         {
             final String nextKey = innerIterator.next().getKey();
-            try
-            {
-                return UserIdentity.fromDelimitedKey( getSessionLabel(), nextKey );
-            }
-            catch ( final PwmUnrecoverableException e )
-            {
-                throw new IllegalStateException( e );
-            }
+            return JsonFactory.get().deserialize( nextKey, UserIdentity.class );
         }
 
         @Override

+ 2 - 2
server/src/main/java/password/pwm/svc/report/ReportService.java

@@ -403,7 +403,7 @@ public class ReportService extends AbstractPwmService implements PwmService
                 final int loopCount = transactionCalculator.getTransactionSize();
                 while ( !cancelFlag.get() && identityQueue.hasNext() && bufferList.size() < loopCount )
                 {
-                    bufferList.add( identityQueue.next().toDelimitedKey() );
+                    bufferList.add( JsonFactory.get().serialize( identityQueue.next() ) );
                 }
                 dnQueue.addAll( bufferList );
                 transactionCalculator.recordLastTransactionDuration( TimeDuration.fromCurrent( loopStart ) );
@@ -487,7 +487,7 @@ public class ReportService extends AbstractPwmService implements PwmService
                 final BlockingThreadPool threadService = new BlockingThreadPool( threadCount, threadName );
                 while ( status() == STATUS.OPEN && !dnQueue.isEmpty() && !cancelFlag.get() )
                 {
-                    final UserIdentity userIdentity = UserIdentity.fromDelimitedKey( getSessionLabel(), dnQueue.poll() );
+                    final UserIdentity userIdentity = JsonFactory.get().deserialize( dnQueue.poll(), UserIdentity.class );
                     if ( getPwmApplication().getConfig().isDevDebugMode() )
                     {
                         LOGGER.trace( getSessionLabel(), () -> "submit " + Instant.now().toString()

+ 11 - 6
server/src/main/java/password/pwm/svc/secure/AbstractSecureService.java

@@ -32,9 +32,9 @@ import password.pwm.svc.PwmService;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.CopyingInputStream;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.json.JsonFactory;
 import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.HmacAlgorithm;
 import password.pwm.util.secure.PwmBlockAlgorithm;
@@ -46,7 +46,6 @@ import password.pwm.util.secure.SecureEngine;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.Serializable;
 import java.security.DigestInputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -171,7 +170,9 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
         return SecureEngine.encryptToString( value, securityKey, defaultBlockAlgorithm, SecureEngine.Flag.URL_SAFE );
     }
 
-    public String encryptObjectToString( final Serializable serializableObject ) throws PwmUnrecoverableException
+    @Override
+    public String encryptObjectToString( final Object serializableObject )
+            throws PwmUnrecoverableException
     {
         final String jsonValue = JsonFactory.get().serialize( serializableObject );
         stats.increment( StatKey.encryptOperations );
@@ -179,7 +180,9 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
         return encryptToString( jsonValue );
     }
 
-    public String encryptObjectToString( final Serializable serializableObject, final PwmSecurityKey securityKey ) throws PwmUnrecoverableException
+    @Override
+    public String encryptObjectToString( final Object serializableObject, final PwmSecurityKey securityKey )
+            throws PwmUnrecoverableException
     {
         final String jsonValue = JsonFactory.get().serialize( serializableObject );
         stats.increment( StatKey.encryptOperations );
@@ -208,7 +211,8 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
         return SecureEngine.decryptStringValue( value, securityKey, defaultBlockAlgorithm, SecureEngine.Flag.URL_SAFE );
     }
 
-    public <T extends Serializable> T decryptObject( final String value, final Class<T> returnClass ) throws PwmUnrecoverableException
+    @Override
+    public <T> T decryptObject( final String value, final Class<T> returnClass ) throws PwmUnrecoverableException
     {
         final String decryptedValue = decryptStringValue( value );
         stats.increment( StatKey.decryptOperations );
@@ -216,7 +220,8 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
         return JsonFactory.get().deserialize( decryptedValue, returnClass );
     }
 
-    public <T extends Serializable> T decryptObject( final String value, final PwmSecurityKey securityKey, final Class<T> returnClass ) throws PwmUnrecoverableException
+    @Override
+    public <T> T decryptObject( final String value, final PwmSecurityKey securityKey, final Class<T> returnClass ) throws PwmUnrecoverableException
     {
         final String decryptedValue = decryptStringValue( value, securityKey );
         stats.increment( StatKey.decryptOperations );

+ 20 - 3
server/src/main/java/password/pwm/svc/secure/SecureService.java

@@ -22,6 +22,7 @@ package password.pwm.svc.secure;
 
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.secure.PwmHashAlgorithm;
+import password.pwm.util.secure.PwmSecurityKey;
 
 import java.io.File;
 import java.io.IOException;
@@ -29,6 +30,8 @@ import java.io.InputStream;
 
 public interface SecureService
 {
+    <T> T decryptObject( String value, PwmSecurityKey securityKey, Class<T> returnClass ) throws PwmUnrecoverableException;
+
     String hash(
             PwmHashAlgorithm pwmHashAlgorithm,
             String input
@@ -38,15 +41,29 @@ public interface SecureService
     String hash(
             byte[] input
     )
-                    throws PwmUnrecoverableException;
+            throws PwmUnrecoverableException;
 
     String hash(
             InputStream input
     )
-                            throws PwmUnrecoverableException;
+            throws PwmUnrecoverableException;
 
     String hash(
             File file
     )
-                                    throws IOException, PwmUnrecoverableException;
+            throws IOException, PwmUnrecoverableException;
+
+    String encryptObjectToString(
+            Object serializableObject
+    )
+            throws PwmUnrecoverableException;
+
+    String encryptObjectToString( Object serializableObject, PwmSecurityKey securityKey ) throws PwmUnrecoverableException;
+
+    <T> T decryptObject(
+            String value,
+            Class<T> returnClass
+    )
+            throws PwmUnrecoverableException;
+
 }

+ 2 - 2
server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java

@@ -237,7 +237,7 @@ public class SessionTrackService extends AbstractPwmService implements PwmServic
     }
 
 
-    private static SessionStateInfoBean infoBeanFromPwmSession( final PwmSession loopSession )
+    private SessionStateInfoBean infoBeanFromPwmSession( final PwmSession loopSession )
     {
         final LocalSessionStateBean loopSsBean = loopSession.getSessionStateBean();
         final LoginInfoBean loginInfoBean = loopSession.getLoginInfoBean();
@@ -273,7 +273,7 @@ public class SessionTrackService extends AbstractPwmService implements PwmServic
             }
             catch ( final PwmUnrecoverableException e )
             {
-                LOGGER.error( loopSession.getLabel(), () -> "unexpected error reading username: " + e.getMessage(), e );
+                LOGGER.error( getSessionLabel(), () -> "unexpected error reading username: " + e.getMessage(), e );
             }
         }
 

+ 1 - 0
server/src/main/java/password/pwm/svc/stats/EpsStatistic.java

@@ -28,6 +28,7 @@ import java.util.Locale;
 public enum EpsStatistic
 {
     REQUESTS(),
+    REST_REQUESTS,
     SESSIONS(),
     PASSWORD_CHANGES(),
     AUTHENTICATION(),

+ 3 - 3
server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java

@@ -33,7 +33,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 
 import java.util.Optional;
 
@@ -70,11 +70,11 @@ class LdapTokenMachine implements TokenMachine
 
         try
         {
-            final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+            final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
             final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                     .filter( searchFilter )
                     .build();
-            final UserIdentity user = userSearchEngine.performSingleUserSearch( searchConfiguration, sessionLabel );
+            final UserIdentity user = userSearchService.performSingleUserSearch( searchConfiguration, sessionLabel );
             if ( user == null )
             {
                 return Optional.empty();

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

@@ -194,7 +194,7 @@ public class SampleDataGenerator
                         PwmSetting.LDAP_PROFILE_DISPLAY_NAME, SAMPLE_USER_LDAP_PROFILE, SAMPLE_USER_DOMAIN ),
                 new LocalizedStringValue( Map.of( "", "ProfileName" ) ), SAMPLE_CONFIG_MODIFIER_IDENTITY );
 
-        return new AppConfig( modifier.newStoredConfiguration() );
+        return AppConfig.forStoredConfig( modifier.newStoredConfiguration() );
     }
 
     private static PwmApplication makeSamplePwmApp()

+ 3 - 3
server/src/main/java/password/pwm/util/cli/commands/ExportResponsesCommand.java

@@ -31,7 +31,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.util.cli.CliException;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.java.TimeDuration;
@@ -86,14 +86,14 @@ public class ExportResponsesCommand extends AbstractCliCommand
     )
             throws PwmUnrecoverableException, PwmOperationalException, IOException, ChaiValidationException
     {
-        final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+        final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
         final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                 .searchTimeout( TimeDuration.MINUTE )
                 .enableValueEscaping( false )
                 .username( "*" )
                 .build();
 
-        final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
+        final Map<UserIdentity, Map<String, String>> results = userSearchService.performMultiUserSearch(
                 searchConfiguration,
                 Integer.MAX_VALUE,
                 Collections.emptyList(),

+ 3 - 3
server/src/main/java/password/pwm/util/cli/commands/ResponseStatsCommand.java

@@ -34,7 +34,7 @@ import password.pwm.config.profile.LdapProfile;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.cr.CrService;
 import password.pwm.util.cli.CliException;
 import password.pwm.util.cli.CliParameters;
@@ -184,7 +184,7 @@ public class ResponseStatsCommand extends AbstractCliCommand
 
         for ( final LdapProfile ldapProfile : pwmDomain.getConfig().getLdapProfiles().values() )
         {
-            final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+            final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
             final TimeDuration searchTimeout = TimeDuration.of(
                     Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT_MS ) ),
                     TimeDuration.Unit.MILLISECONDS );
@@ -198,7 +198,7 @@ public class ResponseStatsCommand extends AbstractCliCommand
                     .ldapProfile( ldapProfile.getIdentifier() )
                     .build();
 
-            final Map<UserIdentity, Map<String, String>> searchResults = userSearchEngine.performMultiUserSearch(
+            final Map<UserIdentity, Map<String, String>> searchResults = userSearchService.performMultiUserSearch(
                     searchConfiguration,
                     Integer.MAX_VALUE,
                     Collections.emptyList(),

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

@@ -91,7 +91,7 @@ public class DebugItemGenerator
         this.sessionLabel = sessionLabel;
 
         final StoredConfiguration obfuscatedStoredConfig = StoredConfigurationUtil.copyConfigAndBlankAllPasswords( pwmApplication.getConfig().getStoredConfiguration() );
-        this.obfuscatedAppConfig = new AppConfig( obfuscatedStoredConfig );
+        this.obfuscatedAppConfig = AppConfig.forStoredConfig( obfuscatedStoredConfig );
     }
 
     private String getFilenameBase()

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

@@ -43,7 +43,7 @@ class LdapConnectionsDebugItemGenerator implements DomainItemGenerator
             throws IOException
     {
         final PwmDomain pwmDomain = debugItemInput.getPwmDomain();
-        final List<LdapDomainService.ConnectionInfo> connectionInfos = pwmDomain.getLdapConnectionService().getConnectionInfos();
+        final List<LdapDomainService.ConnectionInfo> connectionInfos = pwmDomain.getLdapService().getConnectionInfos();
         final String jsonString = JsonFactory.get().serializeCollection( connectionInfos, JsonProvider.Flag.PrettyPrint );
         outputStream.write( jsonString.getBytes( PwmConstants.DEFAULT_CHARSET ) );
     }

+ 3 - 3
server/src/main/java/password/pwm/util/form/FormUtility.java

@@ -42,7 +42,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.user.UserInfo;
 import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.cache.CacheService;
@@ -327,8 +327,8 @@ public class FormUtility
 
         try
         {
-            final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
-            final Map<UserIdentity, Map<String, String>> results = new LinkedHashMap<>( userSearchEngine.performMultiUserSearch(
+            final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
+            final Map<UserIdentity, Map<String, String>> results = new LinkedHashMap<>( userSearchService.performMultiUserSearch(
                     searchConfiguration,
                     resultSearchSizeLimit,
                     Collections.emptyList(),

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

@@ -29,7 +29,6 @@ import java.io.IOException;
 import java.io.ObjectOutputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
-import java.io.Serializable;
 
 public class MiscUtil
 {
@@ -58,7 +57,7 @@ public class MiscUtil
      * @param object object to be analyzed
      * @return size of object (very rough estimate)
      */
-    public static long sizeof( final Serializable object )
+    public static long sizeof( final Object object )
     {
         try ( ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream() )
         {

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

@@ -114,7 +114,7 @@ public class PwmLogManager
     {
         try
         {
-            initConsoleLogger( new AppConfig( StoredConfigurationFactory.newConfig() ), pwmLogLevel );
+            initConsoleLogger( AppConfig.forStoredConfig( StoredConfigurationFactory.newConfig() ), pwmLogLevel );
         }
         catch ( final Exception e )
         {

+ 4 - 2
server/src/main/java/password/pwm/util/logging/PwmLogger.java

@@ -139,7 +139,9 @@ public class PwmLogger
         {
             try
             {
-                final CharSequence cleanedString = PwmLogger.removeUserDataFromString( pwmRequest.getPwmSession().getLoginInfoBean(), message.get() );
+                final CharSequence cleanedString = pwmRequest.hasSession()
+                        ? PwmLogger.removeUserDataFromString( pwmRequest.getPwmSession().getLoginInfoBean(), message.get() )
+                        : message.get();
                 final CharSequence printableString = StringUtil.cleanNonPrintableCharacters( cleanedString );
                 cleanedMessage = () -> printableString;
             }
@@ -157,7 +159,7 @@ public class PwmLogger
             final SessionLabel sessionLabel,
             final Supplier<CharSequence> message,
             final Throwable e
-            )
+    )
     {
         doLogEvent( level, sessionLabel, message, e, null );
     }

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

@@ -287,7 +287,7 @@ public class PasswordUtility
         try
         {
             final PwmPasswordRuleValidator pwmPasswordRuleValidator = PwmPasswordRuleValidator.create( pwmRequest.getLabel(), pwmDomain, userInfo.getPasswordPolicy() );
-            pwmPasswordRuleValidator.testPassword( pwmSession.getLabel(), newPassword, null, userInfo, pwmRequest.getClientConnectionHolder().getActor( ) );
+            pwmPasswordRuleValidator.testPassword( pwmRequest.getLabel(), newPassword, null, userInfo, pwmRequest.getClientConnectionHolder().getActor( ) );
         }
         catch ( final PwmDataValidationException e )
         {
@@ -663,7 +663,7 @@ public class PasswordUtility
             ChaiProvider loopProvider = null;
             try
             {
-                loopProvider = pwmDomain.getLdapConnectionService().getChaiProviderFactory().newProvider( loopConfiguration );
+                loopProvider = pwmDomain.getLdapService().getChaiProviderFactory().newProvider( loopConfiguration );
                 final Instant lastModifiedDate = determinePwdLastModified( pwmDomain, sessionLabel, userIdentity );
                 returnValue.put( loopReplicaUrl, lastModifiedDate );
             }

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

@@ -130,8 +130,8 @@ public class RandomPasswordGenerator
 
         if ( mutatorResult.isValidPassword() )
         {
-            LOGGER.trace( sessionLabel, () -> "finished random password generation after " + mutatorResult.getTryCount()
-                    + " tries.", TimeDuration.fromCurrent( startTime ) );
+            LOGGER.trace( sessionLabel, () -> "finished random password generation after " + mutatorResult.getRounds()
+                    + " rounds.", TimeDuration.fromCurrent( startTime ) );
         }
         else
         {
@@ -141,7 +141,7 @@ public class RandomPasswordGenerator
                 final int errors = pwmPasswordRuleValidator.internalPwmPolicyValidator( mutatorResult.getPassword(), null, null ).size();
                 final int judgeLevel = PasswordUtility.judgePasswordStrength( pwmDomain.getConfig(), mutatorResult.getPassword() );
                 LOGGER.error( sessionLabel, () -> "failed random password generation after "
-                                + mutatorResult.getTryCount() + " tries. " + "(errors=" + errors + ", judgeLevel=" + judgeLevel,
+                                + mutatorResult.getRounds() + " rounds. " + "(errors=" + errors + ", judgeLevel=" + judgeLevel,
                         TimeDuration.fromCurrent( startTime ) );
             }
         }
@@ -159,7 +159,7 @@ public class RandomPasswordGenerator
     {
         private final String password;
         private final boolean validPassword;
-        private final int tryCount;
+        private final int rounds;
     }
 
     private static PwmPasswordPolicy makeRandomGenPwdPolicy(

+ 3 - 3
server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java

@@ -36,7 +36,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.auth.AuthenticationResult;
 import password.pwm.ldap.auth.SimpleLdapAuthenticator;
 import password.pwm.ldap.permission.UserPermissionUtility;
-import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.ldap.search.UserSearchService;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -200,10 +200,10 @@ public class RestAuthenticationProcessor
             return Optional.empty();
         }
 
-        final UserSearchEngine userSearchEngine = pwmDomain.getUserSearchEngine();
+        final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
         try
         {
-            return Optional.of( userSearchEngine.resolveUsername( basicAuthInfo.get().getUsername(), null, null, sessionLabel ) );
+            return Optional.of( userSearchService.resolveUsername( basicAuthInfo.get().getUsername(), null, null, sessionLabel ) );
         }
         catch ( final PwmOperationalException e )
         {

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

@@ -42,7 +42,10 @@ import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmHttpRequestWrapper;
+import password.pwm.http.PwmRequestUtil;
 import password.pwm.http.filter.RequestInitializationFilter;
+import password.pwm.svc.stats.EpsStatistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.JavaHelper;
@@ -102,30 +105,14 @@ public abstract class RestServlet extends HttpServlet
             return;
         }
 
-        final Locale locale = readLocale( pwmApplication, req, resp );
+        final Locale locale = readLocale( pwmDomain.getPwmApplication(), req, resp );
 
-        final SessionLabel sessionLabel;
-        try
-        {
-            sessionLabel =  SessionLabel.builder()
+        final SessionLabel sessionLabel = SessionLabel.builder()
                     .sessionID( "rest-" + REQUEST_COUNTER.next() )
-                    .sourceAddress( RequestInitializationFilter.readUserNetworkAddress( req, pwmApplication.getConfig() ).orElse( "" ) )
-                    .sourceHostname( RequestInitializationFilter.readUserHostname( req, pwmApplication.getConfig() ).orElse( "" ) )
+                    .sourceAddress( PwmRequestUtil.readUserNetworkAddress( req, pwmApplication.getConfig() ).orElse( "" ) )
+                    .sourceHostname( PwmRequestUtil.readUserHostname( req, pwmApplication.getConfig() ).orElse( "" ) )
                     .domain( pwmDomain.getDomainID().stringValue() )
                     .build();
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            final RestResultBean<ErrorInformation> restResultBean  = RestResultBean.fromError(
-                    e.getErrorInformation(),
-                    pwmDomain,
-                    locale,
-                    pwmDomain.getConfig(),
-                    pwmDomain.determineIfDetailErrorMsgShown()
-            );
-            outputRestResultBean( restResultBean, req, resp );
-            return;
-        }
 
         logHttpRequest( pwmApplication, req, sessionLabel );
 
@@ -245,7 +232,9 @@ public abstract class RestServlet extends HttpServlet
         {
             try
             {
-                return ( RestResultBean ) interestedMethod.invoke( this, restRequest );
+                final RestResultBean<?> restResultBean = ( RestResultBean ) interestedMethod.invoke( this, restRequest );
+                StatisticsClient.updateEps( restRequest.getDomain().getPwmApplication(), EpsStatistic.REST_REQUESTS );
+                return restResultBean;
             }
             catch ( final InvocationTargetException e )
             {
@@ -343,11 +332,11 @@ public abstract class RestServlet extends HttpServlet
         }
         else if ( reqAccept.isEmpty() && !anyMatch.isAcceptMatch() )
         {
-            errorMsg = HttpHeader.Accept.getHttpName() + " header is required";
+            errorMsg = HttpHeader.Accept.getHttpName() + " header is missing or has an unexpected value";
         }
         else if ( reqContent.isEmpty() && !anyMatch.isContentMatch() )
         {
-            errorMsg = HttpHeader.ContentType.getHttpName() + " header is required";
+            errorMsg = HttpHeader.ContentType.getHttpName() + " header is missing or has an unexpected value";
         }
         else if ( !anyMatch.isAcceptMatch() )
         {

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