Browse Source

domain configuration management, saving and client connection improvements

Jason Rivard 3 years ago
parent
commit
83398a3c9d
73 changed files with 811 additions and 726 deletions
  1. 17 0
      lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java
  2. 10 1
      server/src/main/java/password/pwm/AppProperty.java
  3. 4 7
      server/src/main/java/password/pwm/PwmAboutProperty.java
  4. 18 23
      server/src/main/java/password/pwm/PwmApplication.java
  5. 44 24
      server/src/main/java/password/pwm/PwmApplicationUtil.java
  6. 23 15
      server/src/main/java/password/pwm/PwmDomain.java
  7. 17 16
      server/src/main/java/password/pwm/PwmDomainUtil.java
  8. 2 4
      server/src/main/java/password/pwm/config/AppConfig.java
  9. 2 4
      server/src/main/java/password/pwm/config/value/OptionListValue.java
  10. 4 1
      server/src/main/java/password/pwm/config/value/StoredValueEncoder.java
  11. 299 0
      server/src/main/java/password/pwm/http/ClientConnectionHolder.java
  12. 3 3
      server/src/main/java/password/pwm/http/ContextManager.java
  13. 1 12
      server/src/main/java/password/pwm/http/HttpEventManager.java
  14. 5 7
      server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java
  15. 66 40
      server/src/main/java/password/pwm/http/PwmRequest.java
  16. 16 7
      server/src/main/java/password/pwm/http/PwmResponse.java
  17. 107 15
      server/src/main/java/password/pwm/http/PwmSession.java
  18. 6 5
      server/src/main/java/password/pwm/http/PwmSessionFactory.java
  19. 0 256
      server/src/main/java/password/pwm/http/SessionManager.java
  20. 2 2
      server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  21. 1 7
      server/src/main/java/password/pwm/http/filter/AuthorizationFilter.java
  22. 1 1
      server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java
  23. 13 13
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  24. 1 0
      server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java
  25. 3 3
      server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java
  26. 5 5
      server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java
  27. 1 1
      server/src/main/java/password/pwm/http/servlet/FullPageHealthServlet.java
  28. 6 6
      server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  29. 1 1
      server/src/main/java/password/pwm/http/servlet/LogoutServlet.java
  30. 3 3
      server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java
  31. 1 1
      server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java
  32. 2 2
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java
  33. 2 2
      server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java
  34. 1 1
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  35. 7 10
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  36. 1 1
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java
  37. 5 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java
  38. 3 8
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  39. 1 1
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  40. 1 1
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  41. 1 1
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  42. 1 1
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java
  43. 1 1
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  44. 3 3
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  45. 1 1
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  46. 2 2
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java
  47. 2 2
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  48. 1 1
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java
  49. 2 2
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java
  50. 2 2
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java
  51. 3 3
      server/src/main/java/password/pwm/http/servlet/updateprofile/UpdateProfileServlet.java
  52. 1 1
      server/src/main/java/password/pwm/http/tag/DisplayTag.java
  53. 1 1
      server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java
  54. 2 2
      server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java
  55. 1 1
      server/src/main/java/password/pwm/http/tag/PwmMacroTag.java
  56. 1 1
      server/src/main/java/password/pwm/http/tag/SuccessMessageTag.java
  57. 4 7
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  58. 3 3
      server/src/main/java/password/pwm/http/tag/value/PwmValue.java
  59. 0 101
      server/src/main/java/password/pwm/ldap/LdapDomainService.java
  60. 3 17
      server/src/main/java/password/pwm/ldap/LdapSystemService.java
  61. 1 1
      server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java
  62. 5 6
      server/src/main/java/password/pwm/svc/email/EmailConnectionPool.java
  63. 1 1
      server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java
  64. 1 1
      server/src/main/java/password/pwm/svc/event/AuditServiceClient.java
  65. 23 23
      server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java
  66. 1 1
      server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java
  67. 23 21
      server/src/main/java/password/pwm/svc/sms/SmsQueueService.java
  68. 1 1
      server/src/main/java/password/pwm/util/CaptchaUtility.java
  69. 6 6
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  70. 0 1
      server/src/main/resources/password/pwm/AppProperty.properties
  71. 3 0
      webapp/src/main/webapp/META-INF/context.xml
  72. 1 1
      webapp/src/main/webapp/public/resources/js/configeditor.js
  73. 5 1
      webapp/src/main/webapp/public/resources/js/main.js

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

@@ -189,4 +189,21 @@ public class CollectionUtil
                 LinkedHashMap::new
         );
     }
+
+    public static <T, K extends Enum<K>, U> Collector<T, ?, Map<K, U>> collectorToEnumMap(
+            final Class<K> keyClass,
+            final Function<? super T, ? extends K> keyMapper,
+            final Function<? super T, ? extends U> valueMapper
+    )
+    {
+        return Collectors.toMap(
+                keyMapper,
+                valueMapper,
+                ( key1, key2 ) ->
+                {
+                    throw new IllegalStateException( "Duplicate key " + key1 );
+                },
+                () -> new EnumMap<>( keyClass )
+        );
+    }
 }

+ 10 - 1
server/src/main/java/password/pwm/AppProperty.java

@@ -20,6 +20,9 @@
 
 package password.pwm;
 
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.ResourceBundle;
 
 /**
@@ -223,7 +226,6 @@ public enum AppProperty
     LDAP_CHAI_SETTINGS                              ( "ldap.chaiSettings" ),
     LDAP_PROXY_CONNECTION_PER_PROFILE               ( "ldap.proxy.connectionsPerProfile" ),
     LDAP_PROXY_MAX_CONNECTIONS                      ( "ldap.proxy.maxConnections" ),
-    LDAP_PROXY_USE_THREAD_LOCAL                     ( "ldap.proxy.useThreadLocal" ),
     LDAP_PROXY_IDLE_THREAD_LOCAL_TIMEOUT_MS         ( "ldap.proxy.idleThreadLocal.timeoutMS" ),
     LDAP_EXTENSIONS_NMAS_ENABLE                     ( "ldap.extensions.nmas.enable" ),
     LDAP_CONNECTION_TIMEOUT                         ( "ldap.connection.timeoutMS" ),
@@ -430,4 +432,11 @@ public enum AppProperty
     {
         return ResourceBundle.getBundle( AppProperty.class.getName() ).getString( key );
     }
+
+    public static Optional<AppProperty> forKey( final String key )
+    {
+        return EnumSet.allOf( AppProperty.class ).stream()
+                .filter( loopProperty -> Objects.equals( loopProperty.getKey(), key ) )
+                .findFirst();
+    }
 }

+ 4 - 7
server/src/main/java/password/pwm/PwmAboutProperty.java

@@ -26,6 +26,7 @@ import password.pwm.i18n.Display;
 import password.pwm.ldap.LdapDomainService;
 import password.pwm.svc.db.DatabaseService;
 import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -37,13 +38,11 @@ import java.nio.charset.Charset;
 import java.security.NoSuchAlgorithmException;
 import java.time.Instant;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.Map;
 import java.util.Optional;
 import java.util.TreeMap;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 public enum PwmAboutProperty
 {
@@ -80,7 +79,7 @@ public enum PwmAboutProperty
     app_ldapProfileCount( null, pwmApplication -> Integer.toString( LdapDomainService.totalLdapProfileCount( pwmApplication ) ) ),
     app_ldapConnectionCount( null, pwmApplication -> Long.toString( LdapDomainService.totalLdapConnectionCount( pwmApplication ) ) ),
     app_activeSessionCount( "App Active Session Count", pwmApplication -> Integer.toString( pwmApplication.getSessionTrackService().sessionCount() ) ),
-    app_activeRequestCount( "App Active Request Count", pwmApplication -> Integer.toString( pwmApplication.getActiveServletRequests().get() ) ),
+    app_activeRequestCount( "App Active Request Count", pwmApplication -> Integer.toString( pwmApplication.getTotalActiveServletRequests() ) ),
     app_definedDomainCount( "App Defined Domain Count", pwmApplication -> Integer.toString( pwmApplication.domains().size() ) ),
 
     build_Time( "Build Time", pwmApplication -> PwmConstants.BUILD_TIME ),
@@ -145,11 +144,9 @@ public enum PwmAboutProperty
                 .stream()
                 .map( aboutProp -> new Pair<>( aboutProp, readAboutValue( pwmApplication, aboutProp ) ) )
                 .filter( entry -> entry.getValue().isPresent() )
-                .collect( Collectors.toMap(
+                .collect( CollectionUtil.collectorToEnumMap( PwmAboutProperty.class,
                         Pair::getKey,
-                        entry -> entry.getValue().get(),
-                        ( k, k2 ) -> k,
-                        () -> new EnumMap<>( PwmAboutProperty.class ) ) ) );
+                        entry -> entry.getValue().get() ) ) );
 
     }
 

+ 18 - 23
server/src/main/java/password/pwm/PwmApplication.java

@@ -62,16 +62,13 @@ import password.pwm.svc.wordlist.WordlistService;
 import password.pwm.util.MBeanUtility;
 import password.pwm.util.PwmScheduler;
 import password.pwm.util.java.FileSystemUtility;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.logging.LocalDBLogger;
-import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogManager;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmRandom;
 
 import java.io.File;
 import java.io.Serializable;
@@ -88,7 +85,6 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -98,10 +94,8 @@ public class PwmApplication
 
     static final int DOMAIN_STARTUP_THREADS = 10;
 
-    private final AtomicInteger activeServletRequests = new AtomicInteger( 0 );
-
     private volatile Map<DomainID, PwmDomain> domains = new HashMap<>();
-    private String runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
+    private String runtimeNonce = PwmApplicationUtil.makeRuntimeNonce();
 
     private final SessionLabel sessionLabel;
     private final PwmServiceManager pwmServiceManager;
@@ -146,7 +140,7 @@ public class PwmApplication
 
     private void initRuntimeNonce()
     {
-        runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
+        runtimeNonce = PwmApplicationUtil.makeRuntimeNonce();
     }
 
     private void initialize()
@@ -259,17 +253,15 @@ public class PwmApplication
             LOGGER.debug( sessionLabel, () -> "no system-level settings have been changed, restart of system services is not required" );
         }
 
-        domains = PwmDomainUtil.reInitDomains( this, newConfig, oldConfig );
+        PwmDomainUtil.reInitDomains( this, newConfig, oldConfig );
 
-        runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
+        runtimeNonce = PwmApplicationUtil.makeRuntimeNonce();
 
         LOGGER.debug( sessionLabel, () -> "completed application restart with " + domains().size() + " domains", TimeDuration.fromCurrent( startTime ) );
     }
 
     private void postInitTasks()
     {
-        System.out.println( JavaHelper.stackTraceToString( new Throwable() ) );
-
         final Instant startTime = Instant.now();
 
         // send system audit event
@@ -287,17 +279,13 @@ public class PwmApplication
         PwmApplicationUtil.outputKeystore( this );
         PwmApplicationUtil.outputTomcatConf( this );
 
-        LOGGER.debug( sessionLabel, () -> "application environment flags: " + JsonFactory.get().serializeCollection( pwmEnvironment.getFlags() ) );
+        LOGGER.debug( sessionLabel, () -> "application environment flags: " + StringUtil.collectionToString( pwmEnvironment.getFlags() ) );
         LOGGER.debug( sessionLabel, () -> "application environment parameters: "
-                + JsonFactory.get().serializeMap( pwmEnvironment.getParameters(), PwmEnvironment.ApplicationParameter.class, String.class ) );
+                + StringUtil.mapToString( pwmEnvironment.getParameters() ) );
 
-        if ( LOGGER.isEnabled( PwmLogLevel.TRACE )
-                && Boolean.parseBoolean( getConfig().readAppProperty( AppProperty.LOGGING_OUTPUT_CONFIGURATION ) ) )
-        {
-            PwmApplicationUtil.outputApplicationInfoToLog( this );
-            PwmApplicationUtil.outputConfigurationToLog( this, DomainID.systemId() );
-            PwmApplicationUtil.outputNonDefaultPropertiesToLog( this );
-        }
+        PwmApplicationUtil.outputApplicationInfoToLog( this );
+        PwmApplicationUtil.outputConfigurationToLog( this, DomainID.systemId() );
+        PwmApplicationUtil.outputNonDefaultPropertiesToLog( this );
 
         MBeanUtility.registerMBean( this );
 
@@ -324,6 +312,11 @@ public class PwmApplication
         return domains;
     }
 
+    protected void setDomains( final Map<DomainID, PwmDomain> domains )
+    {
+        this.domains = Map.copyOf( domains );
+    }
+
     public AppConfig getConfig()
     {
         return pwmEnvironment.getConfig();
@@ -334,9 +327,10 @@ public class PwmApplication
         return pwmEnvironment;
     }
 
-    public AtomicInteger getActiveServletRequests( )
+    public int getTotalActiveServletRequests( )
     {
-        return activeServletRequests;
+        return domains().values().stream().map( domain -> domain.getActiveServletRequests().get() )
+                .reduce( 0, Integer::sum );
     }
 
     public PwmApplicationMode getApplicationMode( )
@@ -882,4 +876,5 @@ public class PwmApplication
         return conditions.stream().allMatch( ( c ) -> c.matches( this ) );
     }
 
+
 }

+ 44 - 24
server/src/main/java/password/pwm/PwmApplicationUtil.java

@@ -285,6 +285,11 @@ class PwmApplicationUtil
 
     static void outputConfigurationToLog( final PwmApplication pwmApplication, final DomainID domainID )
     {
+        if ( !checkIfOutputDumpingEnabled( pwmApplication ) )
+        {
+            return;
+        }
+
         final Instant startTime = Instant.now();
 
         final Function<Map.Entry<String, String>, String> valueFormatter = entry ->
@@ -315,41 +320,56 @@ class PwmApplicationUtil
 
     static void outputNonDefaultPropertiesToLog( final PwmApplication pwmApplication )
     {
-        final Instant startTime = Instant.now();
+        final Map<String, String> data = pwmApplication.getConfig().readAllNonDefaultAppProperties().entrySet().stream()
+                .collect( CollectionUtil.collectorToLinkedMap(
+                        entry -> "AppProperty: " + entry.getKey().getKey(),
+                        Map.Entry::getValue ) );
 
-        final Map<AppProperty, String> nonDefaultProperties = pwmApplication.getConfig().readAllNonDefaultAppProperties();
-        if ( !CollectionUtil.isEmpty( nonDefaultProperties ) )
-        {
-            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--begin non-default app properties output--" );
-            nonDefaultProperties.entrySet().stream()
-                    .map( entry -> "AppProperty: " + entry.getKey().getKey() + " -> " + entry.getValue() )
-                    .map( s -> ( Supplier<CharSequence> ) () -> s )
-                    .forEach( s -> LOGGER.trace( pwmApplication.getSessionLabel(), s ) );
-            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--end non-default app properties output--", TimeDuration.fromCurrent( startTime ) );
-        }
-        else
-        {
-            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "no non-default app properties in configuration" );
-        }
+        outputMapToLog( pwmApplication, data, "non-default app properties" );
     }
 
     static void outputApplicationInfoToLog( final PwmApplication pwmApplication )
     {
-        final Instant startTime = Instant.now();
+        final Map<String, String> data = PwmAboutProperty.makeInfoBean( pwmApplication ).entrySet().stream()
+                .collect( CollectionUtil.collectorToLinkedMap(
+                        entry -> "AboutProperty: " + entry.getKey().getLabel(),
+                        Map.Entry::getValue ) );
+
+        outputMapToLog( pwmApplication, data, "about property info" );
+    }
+
+    private static void outputMapToLog(
+            final PwmApplication pwmApplication,
+            final Map<String, String> input,
+            final String label
+    )
+    {
+        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--begin " + label + "--" );
 
-        final Map<PwmAboutProperty, String> aboutProperties = PwmAboutProperty.makeInfoBean( pwmApplication );
-        if ( !CollectionUtil.isEmpty( aboutProperties ) )
+        if ( !CollectionUtil.isEmpty( input ) )
         {
-            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--begin application info--" );
-            aboutProperties.entrySet().stream()
-                    .map( entry -> "AppProperty: " + entry.getKey().getLabel() + " -> " + entry.getValue() )
-                    .map( s -> ( Supplier<CharSequence> ) () -> s )
+            final String separator = " -> ";
+            input.entrySet().stream()
+                    .map( entry -> ( Supplier<CharSequence> ) () -> entry.getKey() + separator + entry.getValue() )
                     .forEach( s -> LOGGER.trace( pwmApplication.getSessionLabel(), s ) );
-            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--end application info--", TimeDuration.fromCurrent( startTime ) );
         }
         else
         {
-            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "no non-default app properties in configuration" );
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "no " + label + " values" );
         }
+
+        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "--end " + label + "--" );
+    }
+
+    private static boolean checkIfOutputDumpingEnabled( final PwmApplication pwmApplication )
+    {
+        return LOGGER.isEnabled( PwmLogLevel.TRACE )
+                && !pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
+                && Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.LOGGING_OUTPUT_CONFIGURATION ) );
+    }
+
+    static String makeRuntimeNonce()
+    {
+        return PwmRandom.getInstance().randomUUID().toString();
     }
 }

+ 23 - 15
server/src/main/java/password/pwm/PwmDomain.java

@@ -50,17 +50,18 @@ import password.pwm.svc.stats.StatisticsService;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.userhistory.UserHistoryService;
 import password.pwm.svc.wordlist.SharedHistoryService;
+import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.TimeDuration;
-import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
 
 import java.time.Instant;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
- * A repository for objects common to the servlet context.  A singleton
- * of this object is stored in the servlet context.
+ * A representation of a domain in the PwmDomain.  Domains contain settings, functionality and behavior that is 'site' specific.
+ * Domains are sometimes referred to as a "tenant" in other systems.
  *
  * @author Jason D. Rivard
  */
@@ -73,6 +74,13 @@ public class PwmDomain
     private final PwmServiceManager pwmServiceManager;
     private final SessionLabel sessionLabel;
 
+    // for debugging
+    private final int instanceId = DOMAIN_INCREMENTER.next();
+
+    private final AtomicInteger activeServletRequests = new AtomicInteger( 0 );
+
+    private static final AtomicLoopIntIncrementer DOMAIN_INCREMENTER = new AtomicLoopIntIncrementer();
+
     public PwmDomain( final PwmApplication pwmApplication, final DomainID domainID )
     {
         this.pwmApplication = Objects.requireNonNull( pwmApplication );
@@ -90,18 +98,15 @@ public class PwmDomain
 
     {
         final Instant startTime = Instant.now();
-        LOGGER.trace( sessionLabel, () -> "initializing domain " + domainID.stringValue() );
+        LOGGER.trace( sessionLabel, () -> "initializing domain " + domainID.stringValue() + " (instanceId=" + instanceId + ")" );
 
         pwmServiceManager.initAllServices();
 
-        if ( LOGGER.isEnabled( PwmLogLevel.TRACE )
-                && !pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
-                && Boolean.parseBoolean( getConfig().readAppProperty( AppProperty.LOGGING_OUTPUT_CONFIGURATION ) ) )
-        {
-            PwmApplicationUtil.outputConfigurationToLog( pwmApplication, domainID );
-        }
+        PwmApplicationUtil.outputConfigurationToLog( pwmApplication, domainID );
 
-        LOGGER.trace( sessionLabel, () -> "completed initializing domain " + domainID.stringValue(), TimeDuration.fromCurrent( startTime ) );
+        LOGGER.trace( sessionLabel, () -> "completed initializing domain " + domainID.stringValue()
+                        + " (instanceId=" + instanceId + ")",
+                TimeDuration.fromCurrent( startTime ) );
     }
 
     public DomainConfig getConfig( )
@@ -183,11 +188,10 @@ public class PwmDomain
         }
     }
 
-    public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final String identifier )
+    public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final String profileId )
             throws PwmUnrecoverableException
     {
-        Objects.requireNonNull( identifier );
-        return getLdapConnectionService().getProxyChaiProvider( sessionLabel, identifier );
+        return getLdapConnectionService().getProxyChaiProvider( sessionLabel, profileId );
     }
 
     public List<PwmService> getPwmServices( )
@@ -249,7 +253,6 @@ public class PwmDomain
     {
         final Instant startTime = Instant.now();
         LOGGER.trace( sessionLabel, () -> "beginning shutdown domain " + domainID.stringValue() );
-        TimeDuration.SECONDS_10.pause();
         pwmServiceManager.shutdownAllServices();
         LOGGER.trace( sessionLabel, () -> "shutdown domain " + domainID.stringValue() + " completed", TimeDuration.fromCurrent( startTime ) );
     }
@@ -258,6 +261,11 @@ public class PwmDomain
     {
         return domainID;
     }
+
+    public AtomicInteger getActiveServletRequests( )
+    {
+        return activeServletRequests;
+    }
 }
 
 

+ 17 - 16
server/src/main/java/password/pwm/PwmDomainUtil.java

@@ -122,7 +122,7 @@ class PwmDomainUtil
         }
     }
 
-    static Map<DomainID, PwmDomain> reInitDomains(
+    static void reInitDomains(
             final PwmApplication pwmApplication,
             final AppConfig newConfig,
             final AppConfig oldConfig
@@ -130,27 +130,36 @@ class PwmDomainUtil
             throws PwmUnrecoverableException
     {
         final Map<DomainModifyCategory, Set<DomainID>> categorizedDomains = categorizeDomainModifications( newConfig, oldConfig );
+        categorizedDomains.forEach( (  modifyCategory, domainIDSet ) -> domainIDSet.forEach( domainID ->
+                LOGGER.trace( pwmApplication.getSessionLabel(), () -> "domain '" + domainID
+                        + "' configuration modification detected as: " + modifyCategory ) ) );
 
         final Set<PwmDomain> deletedDomains = pwmApplication.domains().entrySet().stream()
                 .filter( e -> categorizedDomains.get( DomainModifyCategory.obsolete ).contains( e.getKey() ) )
                 .map( Map.Entry::getValue ).collect( Collectors.toSet() );
 
+
         final Set<PwmDomain> newDomains = pwmApplication.domains().entrySet().stream()
                 .filter( e -> categorizedDomains.get( DomainModifyCategory.created ).contains( e.getKey() ) )
                 .map( Map.Entry::getValue ).collect( Collectors.toSet() );
 
+
         final Map<DomainID, PwmDomain> returnDomainMap = new TreeMap<>( pwmApplication.domains().entrySet().stream()
                 .filter( e -> categorizedDomains.get( DomainModifyCategory.unchanged ).contains( e.getKey() ) )
                 .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) );
 
         for ( final DomainID modifiedDomainID : categorizedDomains.get( DomainModifyCategory.modified ) )
         {
+            LOGGER.trace( pwmApplication.getSessionLabel(), () -> "domain '" + modifiedDomainID
+                    + "' configuration has changed and requires a restart" );
             deletedDomains.add( pwmApplication.domains().get( modifiedDomainID ) );
             final PwmDomain newDomain = new PwmDomain( pwmApplication, modifiedDomainID );
             newDomains.add( newDomain );
             returnDomainMap.put( modifiedDomainID, newDomain );
         }
 
+        pwmApplication.setDomains( returnDomainMap );
+
         if ( newDomains.isEmpty() && deletedDomains.isEmpty() )
         {
             LOGGER.debug( pwmApplication.getSessionLabel(), () -> "no domain-level settings have been changed, restart of domain services is not required" );
@@ -165,8 +174,6 @@ class PwmDomainUtil
         {
             processDeletedDomains( pwmApplication, deletedDomains );
         }
-
-        return Collections.unmodifiableMap( returnDomainMap );
     }
 
     private static void processDeletedDomains(
@@ -174,22 +181,16 @@ class PwmDomainUtil
             final Set<PwmDomain> deletedDomains
     )
     {
-        // 1 minute later ( to avoid interrupting any in-progress requests, shutdown any obsoleted domains
-        if ( !deletedDomains.isEmpty() )
+        if ( deletedDomains.isEmpty() )
         {
-            pwmApplication.getPwmScheduler().immediateExecuteRunnableInNewThread( () ->
-                    {
-                        TimeDuration.MINUTE.pause();
-                        final Instant startTime = Instant.now();
-                        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "shutting down obsoleted domain services" );
-                        deletedDomains.forEach( PwmDomain::shutdown );
-                        LOGGER.debug( pwmApplication.getSessionLabel(), () -> "shut down obsoleted domain services completed",
-                                TimeDuration.fromCurrent( startTime ) );
-                    },
-                    pwmApplication.getSessionLabel(),
-                    "obsoleted domain cleanup" );
+            return;
         }
 
+        final Instant startTime = Instant.now();
+        LOGGER.trace( pwmApplication.getSessionLabel(), () -> "shutting down obsoleted domain services" );
+        deletedDomains.forEach( PwmDomain::shutdown );
+        LOGGER.debug( pwmApplication.getSessionLabel(), () -> "shut down obsoleted domain services completed",
+                TimeDuration.fromCurrent( startTime ) );
     }
 
     enum DomainModifyCategory

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

@@ -41,7 +41,6 @@ import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
 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.java.TimeDuration;
@@ -104,7 +103,6 @@ public class AppConfig implements SettingReader
     {
         this.storedConfiguration = storedConfiguration;
         this.settingReader = new StoredSettingReader( storedConfiguration, null, DomainID.systemId() );
-
         this.appPropertyOverrides = makeAppPropertyOverrides( settingReader );
 
         this.applicationSecurityKey = makeAppSecurityKey( this );
@@ -190,7 +188,7 @@ public class AppConfig implements SettingReader
     public Map<AppProperty, String> readAllAppProperties()
     {
           return Collections.unmodifiableMap( EnumSet.allOf( AppProperty.class ).stream()
-                  .collect( Collectors.toMap(
+                  .collect( CollectionUtil.collectorToLinkedMap(
                           Function.identity(),
                           this::readAppProperty
                   ) ) );
@@ -344,7 +342,7 @@ public class AppConfig implements SettingReader
         final Map<AppProperty, String> appPropertyMap = new EnumMap<>( AppProperty.class );
         for ( final Map.Entry<String, String> stringEntry : stringMap.entrySet() )
         {
-            JavaHelper.readEnumFromString( AppProperty.class, stringEntry.getKey() )
+            AppProperty.forKey( stringEntry.getKey() )
                     .ifPresent( appProperty ->
                     {
                        final String defaultValue = appProperty.getDefaultValue();

+ 2 - 4
server/src/main/java/password/pwm/config/value/OptionListValue.java

@@ -24,6 +24,7 @@ import org.jrivard.xmlchai.XmlChai;
 import org.jrivard.xmlchai.XmlElement;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.XmlOutputProcessData;
+import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.secure.PwmSecurityKey;
 
@@ -59,10 +60,7 @@ public class OptionListValue extends AbstractValue implements StoredValue
                 {
                     List<String> srcList = JsonFactory.get().deserializeStringList( input );
                     srcList = srcList == null ? Collections.emptyList() : srcList;
-                    while ( srcList.contains( null ) )
-                    {
-                        srcList.remove( null );
-                    }
+                    srcList = CollectionUtil.stripNulls( srcList );
                     return new OptionListValue( Set.copyOf( srcList ) );
                 }
             }

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

@@ -20,13 +20,14 @@
 
 package password.pwm.config.value;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import lombok.Value;
 import password.pwm.PwmConstants;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
-import password.pwm.util.json.JsonFactory;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmRandom;
@@ -51,6 +52,8 @@ public abstract class StoredValueEncoder
         ENCODED( new EncodedModeEngine(), "ENC-PW" + DELIMITER, "ENCODED" + DELIMITER ),;
 
         private final List<String> prefixes;
+
+        @SuppressFBWarnings( "SE_BAD_FIELD" )
         private final SecureOutputEngine secureOutputEngine;
 
         Mode( final SecureOutputEngine secureOutputEngine, final String... prefixes )

+ 299 - 0
server/src/main/java/password/pwm/http/ClientConnectionHolder.java

@@ -0,0 +1,299 @@
+/*
+ * 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 com.novell.ldapchai.ChaiUser;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import com.novell.ldapchai.provider.ChaiProvider;
+import password.pwm.PwmApplication;
+import password.pwm.PwmDomain;
+import password.pwm.bean.DomainID;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.AppConfig;
+import password.pwm.config.profile.LdapProfile;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.LdapOperationsHelper;
+import password.pwm.ldap.auth.AuthenticationType;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.util.PasswordData;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+
+public class ClientConnectionHolder
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( ClientConnectionHolder.class );
+
+    private final PwmApplication pwmApplication;
+    private final Supplier<SessionLabel> sessionLabel;
+    private final DomainID domainID;
+    private final UserIdentity userIdentity;
+    private final AuthenticationType authenticationType;
+
+    private ChaiProvider actorChaiProvider;
+    private final Map<DomainID, Map<String, ChaiProvider>> proxyChaiProviders = new HashMap<>();
+    private final Map<PwmHttpClientConfiguration, PwmHttpClient> httpClients = new HashMap<>();
+
+    private ClientConnectionHolder(
+            final PwmApplication pwmApplication,
+            final UserIdentity userIdentity,
+            final Supplier<SessionLabel> sessionLabel,
+            final ChaiProvider chaiProvider,
+            final AuthenticationType authenticationType,
+            final DomainID domainID
+    )
+    {
+        this.actorChaiProvider = chaiProvider;
+        this.userIdentity = userIdentity;
+        this.authenticationType = Objects.requireNonNull( authenticationType );
+        this.pwmApplication = Objects.requireNonNull( pwmApplication );
+        this.sessionLabel = Objects.requireNonNull( sessionLabel );
+        this.domainID = Objects.requireNonNull( domainID );
+    }
+
+    public static ClientConnectionHolder authenticatedClientConnectionContext(
+            final PwmApplication pwmApplication,
+            final UserIdentity userIdentity,
+            final Supplier<SessionLabel> sessionLabel,
+            final ChaiProvider chaiProvider,
+            final AuthenticationType authenticationType
+    )
+    {
+        return new ClientConnectionHolder( pwmApplication, userIdentity, sessionLabel, chaiProvider, authenticationType, userIdentity.getDomainID() );
+    }
+
+    public static ClientConnectionHolder unauthenticatedClientConnectionContext(
+            final PwmApplication pwmApplication,
+            final DomainID domainID,
+            final Supplier<SessionLabel> sessionLabel
+    )
+    {
+        return new ClientConnectionHolder( pwmApplication, null, sessionLabel, null, AuthenticationType.AUTHENTICATED, domainID );
+    }
+
+    public Optional<UserIdentity> getUserIdentity()
+    {
+        return Optional.of( userIdentity );
+    }
+
+    public ChaiProvider getActorChaiProvider( )
+            throws PwmUnrecoverableException
+    {
+        if ( actorChaiProvider == null )
+        {
+            if ( isAuthenticatedWithoutPasswordAndBind() )
+            {
+                throw PwmUnrecoverableException.newException( PwmError.ERROR_PASSWORD_REQUIRED, "password required for this operation" );
+            }
+            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED, "ldap connection is not available for session" ) );
+        }
+        return actorChaiProvider;
+    }
+
+    public ChaiProvider getProxyChaiProvider( final LdapProfile ldapProfile )
+            throws PwmUnrecoverableException
+    {
+        return getProxyChaiProvider( ldapProfile.getIdentifier() );
+    }
+
+    public ChaiProvider getProxyChaiProvider( final String ldapProfileID )
+            throws PwmUnrecoverableException
+    {
+        final PwmDomain pwmDomain = pwmApplication.domains().get( domainID );
+
+        proxyChaiProviders.computeIfAbsent( domainID, domainID -> new HashMap<>() );
+        final ChaiProvider existingProvider = proxyChaiProviders.get( pwmDomain.getDomainID() ).get( ldapProfileID );
+        if ( existingProvider != null )
+        {
+            return existingProvider;
+        }
+        final ChaiProvider newProvider = pwmDomain.getProxyChaiProvider( sessionLabel.get(), ldapProfileID );
+        proxyChaiProviders.get( pwmDomain.getDomainID() ).put( ldapProfileID, newProvider );
+        return newProvider;
+    }
+
+    public ChaiUser getProxiedChaiUser( final UserIdentity userIdentity )
+            throws PwmUnrecoverableException
+    {
+        try
+        {
+            final ChaiProvider proxiedProvider = getProxyChaiProvider( userIdentity.getLdapProfileID() );
+            return proxiedProvider.getEntryFactory().newChaiUser( userIdentity.getUserDN() );
+        }
+        catch ( final ChaiUnavailableException e )
+        {
+            throw PwmUnrecoverableException.fromChaiException( e );
+        }
+    }
+
+
+    public boolean isAuthenticatedWithoutPasswordAndBind()
+    {
+        return authenticationType == AuthenticationType.AUTH_BIND_INHIBIT;
+    }
+
+    public void updateUserLdapPassword(
+            final UserIdentity userIdentity,
+            final PasswordData userPassword
+    )
+            throws PwmUnrecoverableException
+    {
+
+        this.actorChaiProvider = null;
+
+        final PwmDomain pwmDomain = pwmApplication.domains().get( userIdentity.getDomainID() );
+
+        try
+        {
+            final AppConfig appConfig = pwmDomain.getConfig().getAppConfig();
+            this.actorChaiProvider = LdapOperationsHelper.createChaiProvider(
+                    pwmDomain,
+                    sessionLabel.get(),
+                    userIdentity.getLdapProfile( appConfig ),
+                    pwmDomain.getConfig(),
+                    userIdentity.getUserDN(),
+                    userPassword
+            );
+            final String userDN = userIdentity.getUserDN();
+            actorChaiProvider.getEntryFactory().newChaiEntry( userDN ).exists();
+        }
+        catch ( final ChaiUnavailableException e )
+        {
+            final ErrorInformation errorInformation = new ErrorInformation(
+                    PwmError.ERROR_DIRECTORY_UNAVAILABLE,
+                    "error updating cached chaiProvider connection/password: " + e.getMessage() );
+            throw new PwmUnrecoverableException( errorInformation );
+        }
+    }
+
+
+    public void closeConnections( )
+    {
+        if ( actorChaiProvider != null )
+        {
+            try
+            {
+                LOGGER.debug( sessionLabel.get(), () -> "closing user ldap connection" );
+                actorChaiProvider.close();
+                actorChaiProvider = null;
+            }
+            catch ( final Exception e )
+            {
+                LOGGER.error( sessionLabel.get(), () -> "error while closing user connection: " + e.getMessage() );
+            }
+        }
+
+        for ( final PwmHttpClient pwmHttpClient : httpClients.values() )
+        {
+            LOGGER.debug( sessionLabel.get(), () -> "closing user http client connection: " + pwmHttpClient.toString() );
+            try
+            {
+                pwmHttpClient.close();
+            }
+            catch ( final Exception e )
+            {
+                LOGGER.error( sessionLabel.get(), () -> "error while http client connection: " + e.getMessage() );
+            }
+        }
+
+        httpClients.clear();
+        proxyChaiProviders.clear();
+    }
+
+    public ChaiUser getActor( )
+            throws ChaiUnavailableException, PwmUnrecoverableException
+    {
+
+        if ( userIdentity == null )
+        {
+            throw new IllegalStateException( "user not logged in" );
+        }
+
+        final String userDN = userIdentity.getUserDN();
+
+        if ( StringUtil.isEmpty( userDN ) )
+        {
+            throw new IllegalStateException( "user not logged in" );
+        }
+
+        return this.getActorChaiProvider().getEntryFactory().newChaiUser( userDN );
+    }
+
+    public ChaiUser getActor( final UserIdentity otherIdentity )
+            throws PwmUnrecoverableException
+    {
+        try
+        {
+            if ( !isAuthenticated() )
+            {
+                throw new PwmUnrecoverableException( PwmError.ERROR_AUTHENTICATION_REQUIRED );
+            }
+            if ( userIdentity.getLdapProfileID() == null || otherIdentity.getLdapProfileID() == null )
+            {
+                throw new PwmUnrecoverableException( PwmError.ERROR_NO_LDAP_CONNECTION );
+            }
+            final ChaiProvider provider = this.getActorChaiProvider();
+            return provider.getEntryFactory().newChaiUser( otherIdentity.getUserDN() );
+        }
+        catch ( final ChaiUnavailableException e )
+        {
+            throw PwmUnrecoverableException.fromChaiException( e );
+        }
+    }
+
+    public PwmHttpClient getPwmHttpClient(
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration
+    )
+            throws PwmUnrecoverableException
+    {
+        final PwmHttpClientConfiguration mapKey = pwmHttpClientConfiguration == null
+                ? PwmHttpClientConfiguration.builder().build()
+                : pwmHttpClientConfiguration;
+
+        PwmHttpClient existingClient = httpClients.get( mapKey );
+        if ( existingClient != null && !existingClient.isOpen() )
+        {
+            existingClient = null;
+        }
+        if ( existingClient == null )
+        {
+            final PwmHttpClient newClient = pwmApplication.getHttpClientService().getPwmHttpClient( pwmHttpClientConfiguration, sessionLabel.get() );
+            httpClients.put( mapKey, newClient );
+            return newClient;
+        }
+        return existingClient;
+    }
+
+    private boolean isAuthenticated()
+    {
+        return userIdentity != null;
+    }
+}

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

@@ -554,7 +554,7 @@ public class ContextManager implements Serializable
             final TimeDuration maxRequestWaitTime = TimeDuration.of(
                     Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.APPLICATION_RESTART_MAX_REQUEST_WAIT_MS ) ),
                     TimeDuration.Unit.MILLISECONDS );
-            final int startingRequestInProgress = pwmApplication.getActiveServletRequests().get();
+            final int startingRequestInProgress = pwmApplication.getTotalActiveServletRequests();
 
             if ( startingRequestInProgress == 0 )
             {
@@ -563,10 +563,10 @@ public class ContextManager implements Serializable
 
             LOGGER.trace( SESSION_LABEL, () -> "waiting up to " + maxRequestWaitTime.asCompactString()
                     + " for " + startingRequestInProgress  + " requests to complete." );
-            maxRequestWaitTime.pause( TimeDuration.of( 10, TimeDuration.Unit.MILLISECONDS ), () -> pwmApplication.getActiveServletRequests().get() == 0
+            maxRequestWaitTime.pause( TimeDuration.of( 10, TimeDuration.Unit.MILLISECONDS ), () -> pwmApplication.getTotalActiveServletRequests() == 0
             );
 
-            final int requestsInProgress = pwmApplication.getActiveServletRequests().get();
+            final int requestsInProgress = pwmApplication.getTotalActiveServletRequests();
             final TimeDuration waitTime = TimeDuration.fromCurrent( startTime  );
             LOGGER.trace( SESSION_LABEL, () -> "after " + waitTime.asCompactString() + ", " + requestsInProgress
                     + " requests in progress, proceeding with restart" );

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

@@ -35,7 +35,6 @@ import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
 import javax.servlet.ServletRequestEvent;
 import javax.servlet.ServletRequestListener;
-import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSessionActivationListener;
 import javax.servlet.http.HttpSessionEvent;
@@ -96,7 +95,7 @@ public class HttpEventManager implements
                 if ( httpSession.getAttribute( PwmConstants.SESSION_ATTR_PWM_SESSION ) != null )
                 {
                     final String debugMsg = "destroyed session" + ": " + makeSessionDestroyedDebugMsg( pwmSession );
-                    pwmSession.unauthenticateUser( null );
+                    pwmSession.unAuthenticateUser( null );
 
                     final PwmApplication pwmApplication = ContextManager.getPwmApplication( httpSession.getServletContext() );
                     if ( pwmApplication != null )
@@ -192,16 +191,6 @@ public class HttpEventManager implements
     @Override
     public void requestDestroyed( final ServletRequestEvent sre )
     {
-        try
-        {
-            final PwmRequest pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) sre.getServletRequest(), null );
-            pwmRequest.cleanThreadLocals();
-        }
-        catch ( final Exception e )
-        {
-            LOGGER.debug( () -> "error cleaning request thread locals: " + e.getMessage() );
-        }
-
         ServletRequestListener.super.requestDestroyed( sre );
     }
 

+ 5 - 7
server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java

@@ -51,7 +51,7 @@ public class IdleTimeoutCalculator
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( IdleTimeoutCalculator.class );
 
-    public static MaxIdleTimeoutResult figureMaxSessionTimeout( final PwmDomain pwmDomain, final PwmSession pwmSession )
+    public static MaxIdleTimeoutResult figureMaxSessionTimeout( final PwmDomain pwmDomain, final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
         final DomainConfig domainConfig = pwmDomain.getConfig();
@@ -63,7 +63,7 @@ public class IdleTimeoutCalculator
                     TimeDuration.of( idleSetting, TimeDuration.Unit.SECONDS ) ) );
         }
 
-        if ( !pwmSession.isAuthenticated() )
+        if ( !pwmRequest.isAuthenticated() )
         {
             if ( pwmDomain.getApplicationMode() == PwmApplicationMode.NEW )
             {
@@ -91,9 +91,8 @@ public class IdleTimeoutCalculator
         }
         else
         {
-            final UserInfo userInfo = pwmSession.getUserInfo();
-            final boolean userIsAdmin = pwmSession.isAuthenticated()
-                    && pwmSession.getSessionManager().checkPermission( pwmDomain, Permission.PWMADMIN );
+            final UserInfo userInfo = pwmRequest.getPwmSession().getUserInfo();
+            final boolean userIsAdmin = pwmRequest.isAuthenticated() && pwmRequest.checkPermission( Permission.PWMADMIN );
             final Set<MaxIdleTimeoutResult> loggedInResults = figureMaxAuthUserTimeout( domainConfig, userInfo, userIsAdmin );
             results.addAll( loggedInResults );
         }
@@ -179,11 +178,10 @@ public class IdleTimeoutCalculator
     {
         final PwmURL pwmURL = pwmRequest.getURL();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
 
         if ( pwmURL.isResourceURL() )
         {
-            return figureMaxSessionTimeout( pwmDomain, pwmSession ).getIdleTimeout();
+            return figureMaxSessionTimeout( pwmDomain, pwmRequest ).getIdleTimeout();
         }
 
         for ( final IdleTimeoutCalculatorModule module : SERVLET_IDLE_CALCULATORS )

+ 66 - 40
server/src/main/java/password/pwm/http/PwmRequest.java

@@ -25,6 +25,7 @@ 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;
@@ -48,6 +49,7 @@ 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;
@@ -56,12 +58,12 @@ import password.pwm.http.servlet.PwmRequestID;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.user.UserInfo;
 import password.pwm.util.Validator;
-import password.pwm.data.ImmutableByteArray;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.secure.PwmSecurityKey;
 import password.pwm.ws.server.RestResultBean;
 
@@ -75,7 +77,6 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -95,14 +96,13 @@ public class PwmRequest extends PwmHttpRequestWrapper
 
     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 final Set<ThreadLocal> threadLocals = new HashSet<>();
-
     private static final Lock CREATE_LOCK = new ReentrantLock();
 
     public static PwmRequest forRequest(
@@ -143,24 +143,25 @@ public class PwmRequest extends PwmHttpRequestWrapper
         this.pwmURL = PwmURL.create( this.getHttpServletRequest() );
         this.domainID = PwmHttpRequestWrapper.readDomainIdFromRequest( httpServletRequest );
         this.sessionLabel = makeSessionLabel();
+        this.pwmRequestContext = makePwmRequestContext();
     }
 
-    public PwmDomain getPwmDomain( )
+    public PwmDomain getPwmDomain()
     {
         return pwmApplication.domains().get( getDomainID() );
     }
 
-    public PwmSession getPwmSession( )
+    public PwmSession getPwmSession()
     {
         return PwmSessionFactory.readPwmSession( this.getHttpServletRequest().getSession(), getPwmDomain() );
     }
 
-    public SessionLabel getLabel( )
+    public SessionLabel getLabel()
     {
         return sessionLabel;
     }
 
-    private SessionLabel makeSessionLabel( )
+    private SessionLabel makeSessionLabel()
     {
         if ( getHttpServletRequest().getSession( false ) == null )
         {
@@ -176,12 +177,12 @@ public class PwmRequest extends PwmHttpRequestWrapper
                 .build();
     }
 
-    public PwmResponse getPwmResponse( )
+    public PwmResponse getPwmResponse()
     {
         return pwmResponse;
     }
 
-    public Locale getLocale( )
+    public Locale getLocale()
     {
         if ( isFlag( PwmRequestFlag.INCLUDE_CONFIG_CSS ) )
         {
@@ -230,7 +231,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
         this.getPwmResponse().outputJsonResult( restResultBean );
     }
 
-    public ContextManager getContextManager( )
+    public ContextManager getContextManager()
             throws PwmUnrecoverableException
     {
         return ContextManager.getContextManager( this );
@@ -315,7 +316,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
         private final ImmutableByteArray content;
     }
 
-    public UserIdentity getUserInfoIfLoggedIn( )
+    public UserIdentity getUserInfoIfLoggedIn()
     {
         final PwmSession pwmSession = getPwmSession();
         return pwmSession.isAuthenticated()
@@ -324,7 +325,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
     }
 
 
-    public void validatePwmFormID( )
+    public void validatePwmFormID()
             throws PwmUnrecoverableException
     {
         Validator.validatePwmFormID( this );
@@ -395,7 +396,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return ( Serializable ) this.getHttpServletRequest().getAttribute( name.toString() );
     }
 
-    public PwmURL getURL( )
+    public PwmURL getURL()
     {
         return pwmURL;
     }
@@ -412,12 +413,13 @@ public class PwmRequest extends PwmHttpRequestWrapper
         }
     }
 
-    public boolean isAuthenticated( )
+    public boolean isAuthenticated()
     {
         return getPwmSession().isAuthenticated();
     }
 
-    public boolean isForcedPageView( ) throws PwmUnrecoverableException
+    public boolean isForcedPageView()
+            throws PwmUnrecoverableException
     {
         if ( !isAuthenticated() )
         {
@@ -472,14 +474,14 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return flags.contains( flag );
     }
 
-    public boolean hasForwardUrl( )
+    public boolean hasForwardUrl()
     {
         final LocalSessionStateBean ssBean = this.getPwmSession().getSessionStateBean();
         final String redirectURL = ssBean.getForwardURL();
         return StringUtil.notEmpty( redirectURL );
     }
 
-    public String getForwardUrl( )
+    public String getForwardUrl()
     {
         final LocalSessionStateBean ssBean = this.getPwmSession().getSessionStateBean();
         String redirectURL = ssBean.getForwardURL();
@@ -508,7 +510,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return ssBean.getLogoutURL() == null ? pwmApplication.getConfig().readSettingAsString( PwmSetting.URL_LOGOUT ) : ssBean.getLogoutURL();
     }
 
-    public String getCspNonce( )
+    public String getCspNonce()
             throws PwmUnrecoverableException
     {
         cspCreationLock.lock();
@@ -545,7 +547,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
     }
 
     @Override
-    public String toString( )
+    public String toString()
     {
         return this.getClass().getSimpleName() + " "
                 + ( this.getLabel() == null ? "" : getLabel().toString() )
@@ -581,19 +583,20 @@ public class PwmRequest extends PwmHttpRequestWrapper
         this.setAttribute( PwmRequestAttribute.FormShowPasswordFields, showPasswordFields );
     }
 
-    public void invalidateSession( )
+    public void invalidateSession()
             throws PwmUnrecoverableException
     {
-        this.getPwmSession().unauthenticateUser( this );
+        this.getPwmSession().unAuthenticateUser( this );
         this.getHttpServletRequest().getSession().invalidate();
     }
 
-    public String getURLwithQueryString( ) throws PwmUnrecoverableException
+    public String getURLwithQueryString()
+            throws PwmUnrecoverableException
     {
         return PwmURL.appendAndEncodeUrlParameters( getURLwithoutQueryString(), readParametersAsMap() );
     }
 
-    public boolean endUserFunctionalityAvailable( )
+    public boolean endUserFunctionalityAvailable()
     {
         final PwmApplicationMode mode = pwmApplication.getApplicationMode();
         if ( mode == PwmApplicationMode.NEW )
@@ -611,12 +614,12 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return false;
     }
 
-    public String getContextPath( )
+    public String getContextPath()
     {
         return this.getHttpServletRequest().getContextPath();
     }
 
-    public String getBasePath( )
+    public String getBasePath()
     {
         final String rawContextPath = this.getHttpServletRequest().getContextPath();
 
@@ -630,6 +633,11 @@ public class PwmRequest extends PwmHttpRequestWrapper
     }
 
     public PwmRequestContext getPwmRequestContext()
+    {
+        return pwmRequestContext;
+    }
+
+    private PwmRequestContext makePwmRequestContext()
     {
         return new PwmRequestContext( pwmApplication, this.getDomainID(), this.getLabel(), this.getLocale(), pwmRequestID );
     }
@@ -659,7 +667,8 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return pwmApplication;
     }
 
-    private Profile getProfile( final PwmDomain pwmDomain, final ProfileDefinition profileDefinition ) throws PwmUnrecoverableException
+    private Profile getProfile( final PwmDomain pwmDomain, final ProfileDefinition profileDefinition )
+            throws PwmUnrecoverableException
     {
         if ( profileDefinition.isAuthenticated() && !getPwmSession().isAuthenticated() )
         {
@@ -674,54 +683,71 @@ public class PwmRequest extends PwmHttpRequestWrapper
         throw new PwmUnrecoverableException( PwmError.ERROR_NO_PROFILE_ASSIGNED );
     }
 
-    public HelpdeskProfile getHelpdeskProfile() throws PwmUnrecoverableException
+    public HelpdeskProfile getHelpdeskProfile()
+            throws PwmUnrecoverableException
     {
         return ( HelpdeskProfile ) getProfile( getPwmDomain(), ProfileDefinition.Helpdesk );
     }
 
-    public SetupOtpProfile getSetupOTPProfile() throws PwmUnrecoverableException
+    public SetupOtpProfile getSetupOTPProfile()
+            throws PwmUnrecoverableException
     {
         return ( SetupOtpProfile ) getProfile( getPwmDomain(), ProfileDefinition.SetupOTPProfile );
     }
 
-    public SetupResponsesProfile getSetupResponsesProfile() throws PwmUnrecoverableException
+    public SetupResponsesProfile getSetupResponsesProfile()
+            throws PwmUnrecoverableException
     {
         return ( SetupResponsesProfile ) getProfile( getPwmDomain(), ProfileDefinition.SetupResponsesProfile );
     }
 
-    public UpdateProfileProfile getUpdateAttributeProfile() throws PwmUnrecoverableException
+    public UpdateProfileProfile getUpdateAttributeProfile()
+            throws PwmUnrecoverableException
     {
         return ( UpdateProfileProfile ) getProfile( getPwmDomain(), ProfileDefinition.UpdateAttributes );
     }
 
-    public PeopleSearchProfile getPeopleSearchProfile() throws PwmUnrecoverableException
+    public PeopleSearchProfile getPeopleSearchProfile()
+            throws PwmUnrecoverableException
     {
         return ( PeopleSearchProfile ) getProfile( getPwmDomain(), ProfileDefinition.PeopleSearch );
     }
 
-    public DeleteAccountProfile getSelfDeleteProfile() throws PwmUnrecoverableException
+    public DeleteAccountProfile getSelfDeleteProfile()
+            throws PwmUnrecoverableException
     {
         return ( DeleteAccountProfile ) getProfile( getPwmDomain(), ProfileDefinition.DeleteAccount );
     }
 
-    public ChangePasswordProfile getChangePasswordProfile() throws PwmUnrecoverableException
+    public ChangePasswordProfile getChangePasswordProfile()
+            throws PwmUnrecoverableException
     {
         return ( ChangePasswordProfile ) getProfile( getPwmDomain(), ProfileDefinition.ChangePassword );
     }
 
-    public AccountInformationProfile getAccountInfoProfile() throws PwmUnrecoverableException
+    public AccountInformationProfile getAccountInfoProfile()
+            throws PwmUnrecoverableException
     {
         return ( AccountInformationProfile ) getProfile( getPwmDomain(), ProfileDefinition.AccountInformation );
     }
 
-    public void registerThreadLocal( final ThreadLocal<?> threadLocal )
+    public MacroRequest getMacroMachine()
+            throws PwmUnrecoverableException
+    {
+        return getPwmSession().getMacroMachine( getPwmRequestContext() );
+    }
+
+    public boolean checkPermission(
+            final Permission permission
+    )
+            throws PwmUnrecoverableException
     {
-        threadLocals.add( threadLocal );
+        return getPwmSession().checkPermission( getPwmRequestContext(), permission );
     }
 
-    public void cleanThreadLocals()
+    public ClientConnectionHolder getClientConnectionHolder()
     {
-        threadLocals.forEach( ThreadLocal::remove );
-        threadLocals.clear();
+        return getPwmSession().getClientConnectionHolder( getPwmApplication() );
     }
 }
+

+ 16 - 7
server/src/main/java/password/pwm/http/PwmResponse.java

@@ -102,10 +102,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
     )
             throws ServletException, IOException, PwmUnrecoverableException
     {
-        if ( !pwmRequest.isFlag( PwmRequestFlag.NO_REQ_COUNTER ) )
-        {
-            pwmRequest.getPwmSession().getSessionManager().incrementRequestCounterKey();
-        }
+        incrementRequestCounterKey( pwmRequest );
 
         preCommitActions();
 
@@ -123,6 +120,19 @@ public class PwmResponse extends PwmHttpResponseWrapper
         servletContext.getRequestDispatcher( url ).forward( httpServletRequest, this.getHttpServletResponse() );
     }
 
+    private static void incrementRequestCounterKey( final PwmRequest pwmRequest )
+    {
+        if ( pwmRequest.isFlag( PwmRequestFlag.NO_REQ_COUNTER ) )
+        {
+            return;
+        }
+
+        final int nextCounter = pwmRequest.getPwmSession().getLoginInfoBean().getReqCounter() + 1;
+        pwmRequest.getPwmSession().getLoginInfoBean().setReqCounter( nextCounter );
+
+        LOGGER.trace( pwmRequest, () -> "incremented request counter to " + nextCounter );
+    }
+
     public void forwardToSuccessPage( final Message message, final String... field )
             throws ServletException, PwmUnrecoverableException, IOException
 
@@ -132,7 +142,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
     }
 
     public void forwardToSuccessPage( final String message, final Flag... flags )
-            throws ServletException, PwmUnrecoverableException, IOException
+            throws ServletException, IOException
 
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
@@ -174,7 +184,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
         if ( JavaHelper.enumArrayContainsValue( flags, Flag.ForceLogout ) )
         {
             LOGGER.debug( pwmRequest, () -> "forcing logout due to error " + errorInformation.toDebugStr() );
-            pwmRequest.getPwmSession().unauthenticateUser( pwmRequest );
+            pwmRequest.getPwmSession().unAuthenticateUser( pwmRequest );
         }
 
         if ( getResponseFlags().contains( PwmResponseFlag.ERROR_RESPONSE_SENT ) )
@@ -228,7 +238,6 @@ public class PwmResponse extends PwmHttpResponseWrapper
         final String outputString = restResultBean.toJson( pwmRequest.isPrettyPrintJsonParameterTrue() );
         resp.setContentType( HttpContentType.json.getHeaderValueWithEncoding() );
         resp.getWriter().print( outputString );
-        resp.getWriter().close();
     }
 
 

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

@@ -21,8 +21,10 @@
 package password.pwm.http;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import lombok.Data;
+import lombok.EqualsAndHashCode;
 import password.pwm.AppProperty;
+import password.pwm.Permission;
+import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
@@ -30,22 +32,27 @@ 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;
 import password.pwm.http.bean.UserSessionDataCacheBean;
-import password.pwm.user.UserInfo;
-import password.pwm.user.UserInfoBean;
 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.json.JsonFactory;
 import password.pwm.util.java.TimeDuration;
+import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmSecurityKey;
 
@@ -63,7 +70,7 @@ import java.util.concurrent.locks.ReentrantLock;
 /**
  * @author Jason D. Rivard
  */
-@Data
+@EqualsAndHashCode
 public class PwmSession implements Serializable
 {
     private static final long serialVersionUID = 1L;
@@ -83,7 +90,7 @@ public class PwmSession implements Serializable
     private static final Lock CREATION_LOCK = new ReentrantLock();
 
     private final Lock securityKeyLock = new ReentrantLock();
-    private final transient SessionManager sessionManager;
+    private transient ClientConnectionHolder clientConnectionHolder;
 
     public static PwmSession createPwmSession( final PwmDomain pwmDomain )
     {
@@ -110,15 +117,27 @@ public class PwmSession implements Serializable
         StatisticsClient.incrementStat( pwmDomain.getPwmApplication(), Statistic.HTTP_SESSIONS );
 
         pwmDomain.getSessionTrackService().addSessionData( this );
-        this.sessionManager = new SessionManager( pwmDomain, this );
 
         LOGGER.trace( () -> "created new session" );
     }
 
 
-    public SessionManager getSessionManager( )
+    public ClientConnectionHolder getClientConnectionHolder( final PwmApplication pwmApplication )
     {
-        return sessionManager;
+        if ( clientConnectionHolder != null )
+        {
+            if ( isAuthenticated() && !Objects.equals( clientConnectionHolder.getUserIdentity().orElse( null ), getUserInfo().getUserIdentity() ) )
+            {
+                clientConnectionHolder = null;
+            }
+        }
+
+        if ( clientConnectionHolder == null )
+        {
+            clientConnectionHolder = ClientConnectionHolder.unauthenticatedClientConnectionContext( pwmApplication, domainID, this::getLabel );
+        }
+
+        return clientConnectionHolder;
     }
 
     public LocalSessionStateBean getSessionStateBean( )
@@ -202,7 +221,6 @@ public class PwmSession implements Serializable
 
     public SessionLabel getLabel( )
     {
-        System.out.println();
         final LocalSessionStateBean ssBean = this.getSessionStateBean();
 
         UserIdentity userIdentity = null;
@@ -216,7 +234,7 @@ public class PwmSession implements Serializable
                 final UserInfo userInfo = getUserInfo();
                 userIdentity = userInfo.getUserIdentity();
                 userID = userInfo.getUsername();
-                profile = userIdentity.getLdapProfileID();
+                profile = userIdentity == null ? null : userIdentity.getLdapProfileID();
             }
             catch ( final PwmUnrecoverableException e )
             {
@@ -236,11 +254,11 @@ public class PwmSession implements Serializable
     }
 
     /**
-     * Unauthenticate the pwmSession.
+     * UnAuthenticate the pwmSession.
      *
      * @param pwmRequest current request of the user
      */
-    public void unauthenticateUser( final PwmRequest pwmRequest )
+    public void unAuthenticateUser( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
         final LocalSessionStateBean ssBean = getSessionStateBean();
@@ -252,7 +270,7 @@ public class PwmSession implements Serializable
 
             final StringBuilder sb = new StringBuilder();
 
-            sb.append( "unauthenticate session from " ).append( ssBean.getSrcAddress() );
+            sb.append( "un-authenticate session from " ).append( ssBean.getSrcAddress() );
             if ( getUserInfo().getUserIdentity() != null )
             {
                 sb.append( " (" ).append( getUserInfo().getUserIdentity() ).append( ')' );
@@ -262,7 +280,8 @@ public class PwmSession implements Serializable
             this.getLoginInfoBean().setAuthenticated( false );
 
             // close out any outstanding connections
-            getSessionManager().closeConnections();
+            getClientConnectionHolder( pwmRequest.getPwmApplication() ).closeConnections();
+            clientConnectionHolder = null;
 
             LOGGER.debug( pwmRequest, sb::toString );
         }
@@ -417,4 +436,77 @@ public class PwmSession implements Serializable
             securityKeyLock.unlock();
         }
     }
+
+    public void updateLdapAuthentication(
+            final PwmApplication pwmApplication,
+            final UserIdentity userIdentity,
+            final AuthenticationResult authenticationResult
+    )
+    {
+        clientConnectionHolder = ClientConnectionHolder.authenticatedClientConnectionContext(
+                pwmApplication,
+                userIdentity,
+                this::getLabel,
+                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
+    )
+            throws PwmUnrecoverableException
+    {
+        final UserInfo userInfoBean = isAuthenticated()
+                ? getUserInfo()
+                : null;
+        return MacroRequest.forUser( pwmRequestContext.getPwmApplication(), pwmRequestContext.getSessionLabel(), userInfoBean, getLoginInfoBean() );
+    }
 }

+ 6 - 5
server/src/main/java/password/pwm/http/PwmSessionFactory.java

@@ -37,10 +37,11 @@ public class PwmSessionFactory
 
     private PwmSessionFactory( )
     {
-
     }
 
+
     public static void sessionMerge(
+            final PwmRequest pwmRequest,
             final PwmDomain pwmDomain,
             final PwmSession pwmSession,
             final HttpSession httpSession
@@ -49,7 +50,7 @@ public class PwmSessionFactory
     {
         httpSession.setAttribute( PwmConstants.SESSION_ATTR_PWM_SESSION, pwmSession );
 
-        setHttpSessionIdleTimeout( pwmDomain, pwmSession, httpSession );
+        setHttpSessionIdleTimeout( pwmDomain, pwmRequest, httpSession );
     }
 
     private static PwmSession createSession( final PwmDomain pwmDomain )
@@ -79,16 +80,16 @@ public class PwmSessionFactory
 
     public static void setHttpSessionIdleTimeout(
             final PwmDomain pwmDomain,
-            final PwmSession pwmSession,
+            final PwmRequest pwmRequest,
             final HttpSession httpSession
     )
             throws PwmUnrecoverableException
     {
-        final IdleTimeoutCalculator.MaxIdleTimeoutResult result = IdleTimeoutCalculator.figureMaxSessionTimeout( pwmDomain, pwmSession );
+        final IdleTimeoutCalculator.MaxIdleTimeoutResult result = IdleTimeoutCalculator.figureMaxSessionTimeout( pwmDomain, pwmRequest );
         if ( httpSession.getMaxInactiveInterval() != result.getIdleTimeout().as( TimeDuration.Unit.SECONDS ) )
         {
             httpSession.setMaxInactiveInterval( ( int ) result.getIdleTimeout().as( TimeDuration.Unit.SECONDS ) );
-            LOGGER.trace( pwmSession.getLabel(), () -> "setting java servlet session timeout to " + result.getIdleTimeout().asCompactString()
+            LOGGER.trace( pwmRequest, () -> "setting java servlet session timeout to " + result.getIdleTimeout().asCompactString()
                     + " due to " + result.getReason() );
         }
     }

+ 0 - 256
server/src/main/java/password/pwm/http/SessionManager.java

@@ -1,256 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2021 The PWM Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package password.pwm.http;
-
-import com.novell.ldapchai.ChaiUser;
-import com.novell.ldapchai.exception.ChaiUnavailableException;
-import com.novell.ldapchai.provider.ChaiProvider;
-import password.pwm.Permission;
-import password.pwm.PwmDomain;
-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.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.user.UserInfo;
-import password.pwm.ldap.auth.AuthenticationType;
-import password.pwm.ldap.permission.UserPermissionUtility;
-import password.pwm.util.PasswordData;
-import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroRequest;
-
-import java.util.List;
-
-/**
- * Wraps an <i>HttpSession</i> to provide additional PWM-related session
- * management activities.
- *
- * @author Jason D. Rivard
- */
-public class SessionManager
-{
-
-    private static final PwmLogger LOGGER = PwmLogger.forClass( SessionManager.class );
-
-    private volatile ChaiProvider chaiProvider;
-
-    private final PwmDomain pwmDomain;
-    private final PwmSession pwmSession;
-
-    public SessionManager( final PwmDomain pwmDomain, final PwmSession pwmSession )
-    {
-        this.pwmDomain = pwmDomain;
-        this.pwmSession = pwmSession;
-    }
-
-    public ChaiProvider getChaiProvider( )
-            throws PwmUnrecoverableException
-    {
-        if ( chaiProvider == null )
-        {
-            if ( isAuthenticatedWithoutPasswordAndBind() )
-            {
-                throw PwmUnrecoverableException.newException( PwmError.ERROR_PASSWORD_REQUIRED, "password required for this operation" );
-            }
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED, "ldap connection is not available for session" ) );
-        }
-        return chaiProvider;
-    }
-
-    public boolean isAuthenticatedWithoutPasswordAndBind()
-    {
-        return pwmSession.getLoginInfoBean().getUserCurrentPassword() == null
-                && pwmSession.getLoginInfoBean().getType() == AuthenticationType.AUTH_WITHOUT_PASSWORD
-                && chaiProvider == null;
-
-    }
-
-    public void setChaiProvider( final ChaiProvider chaiProvider )
-    {
-        this.chaiProvider = chaiProvider;
-    }
-
-    public void updateUserPassword( final UserIdentity userIdentity, final PasswordData userPassword )
-            throws PwmUnrecoverableException
-    {
-        this.closeConnections();
-
-        try
-        {
-            final AppConfig appConfig = pwmDomain.getConfig().getAppConfig();
-            this.chaiProvider = LdapOperationsHelper.createChaiProvider(
-                    pwmDomain,
-                    pwmSession.getLabel(),
-                    userIdentity.getLdapProfile( appConfig ),
-                    pwmDomain.getConfig(),
-                    userIdentity.getUserDN(),
-                    userPassword
-            );
-            final String userDN = userIdentity.getUserDN();
-            chaiProvider.getEntryFactory().newChaiEntry( userDN ).exists();
-        }
-        catch ( final ChaiUnavailableException e )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation(
-                    PwmError.ERROR_DIRECTORY_UNAVAILABLE,
-                    "error updating cached chaiProvider connection/password: " + e.getMessage() );
-            throw new PwmUnrecoverableException( errorInformation );
-        }
-    }
-
-
-    public void closeConnections( )
-    {
-        if ( chaiProvider != null )
-        {
-            try
-            {
-                LOGGER.debug( pwmSession.getLabel(), () -> "closing user ldap connection" );
-                chaiProvider.close();
-                chaiProvider = null;
-            }
-            catch ( final Exception e )
-            {
-                LOGGER.error( pwmSession.getLabel(), () -> "error while closing user connection: " + e.getMessage() );
-            }
-        }
-    }
-
-    public ChaiUser getActor( )
-            throws ChaiUnavailableException, PwmUnrecoverableException
-    {
-
-        if ( !pwmSession.isAuthenticated() )
-        {
-            throw new IllegalStateException( "user not logged in" );
-        }
-
-        final UserIdentity userDN = pwmSession.getUserInfo().getUserIdentity();
-
-        if ( userDN == null || userDN.getUserDN() == null || userDN.getUserDN().length() < 1 )
-        {
-            throw new IllegalStateException( "user not logged in" );
-        }
-
-        return this.getChaiProvider().getEntryFactory().newChaiUser( userDN.getUserDN() );
-    }
-
-    public ChaiUser getActor( final UserIdentity userIdentity )
-            throws PwmUnrecoverableException
-    {
-        try
-        {
-            if ( !pwmSession.isAuthenticated() )
-            {
-                throw new PwmUnrecoverableException( PwmError.ERROR_AUTHENTICATION_REQUIRED );
-            }
-            final UserIdentity thisIdentity = pwmSession.getUserInfo().getUserIdentity();
-            if ( thisIdentity.getLdapProfileID() == null || userIdentity.getLdapProfileID() == null )
-            {
-                throw new PwmUnrecoverableException( PwmError.ERROR_NO_LDAP_CONNECTION );
-            }
-            final ChaiProvider provider = this.getChaiProvider();
-            return provider.getEntryFactory().newChaiUser( userIdentity.getUserDN() );
-        }
-        catch ( final ChaiUnavailableException e )
-        {
-            throw PwmUnrecoverableException.fromChaiException( e );
-        }
-    }
-
-    public void incrementRequestCounterKey( )
-    {
-        if ( this.pwmSession != null )
-        {
-            this.pwmSession.getLoginInfoBean().setReqCounter(
-                    this.pwmSession.getLoginInfoBean().getReqCounter() + 1 );
-
-            LOGGER.trace( pwmSession.getLabel(), () -> "incremented request counter to " + this.pwmSession.getLoginInfoBean().getReqCounter() );
-        }
-    }
-
-    public boolean checkPermission( final PwmDomain pwmDomain, final Permission permission )
-            throws PwmUnrecoverableException
-    {
-        final boolean devDebugMode = pwmDomain.getConfig().getAppConfig().isDevDebugMode();
-        if ( devDebugMode )
-        {
-            LOGGER.trace( pwmSession.getLabel(), () -> String.format( "entering checkPermission(%s, %s, %s)", permission, pwmSession, pwmDomain ) );
-        }
-
-        if ( !pwmSession.isAuthenticated() )
-        {
-            if ( devDebugMode )
-            {
-                LOGGER.trace( pwmSession.getLabel(), () -> "user is not authenticated, returning false for permission check" );
-            }
-            return false;
-        }
-
-        Permission.PermissionStatus status = pwmSession.getUserSessionDataCacheBean().getPermission( permission );
-        if ( status == Permission.PermissionStatus.UNCHECKED )
-        {
-            if ( devDebugMode )
-            {
-                LOGGER.debug( pwmSession.getLabel(),
-                        () -> String.format( "checking permission %s for user %s", permission.toString(), pwmSession.getUserInfo().getUserIdentity().toDelimitedKey() ) );
-            }
-
-            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, pwmSession.getLabel(), pwmSession.getUserInfo().getUserIdentity(), userPermission );
-                status = result ? Permission.PermissionStatus.GRANTED : Permission.PermissionStatus.DENIED;
-            }
-
-            pwmSession.getUserSessionDataCacheBean().setPermission( permission, status );
-
-            {
-                final Permission.PermissionStatus finalStatus = status;
-                LOGGER.debug( pwmSession.getLabel(),
-                        () -> String.format( "permission %s for user %s is %s",
-                                permission,
-                                pwmSession.isAuthenticated()
-                                        ? pwmSession.getUserInfo().getUserIdentity().toDelimitedKey()
-                                        : "[unauthenticated]",
-                                finalStatus ) );
-            }
-        }
-        return status == Permission.PermissionStatus.GRANTED;
-    }
-
-    public MacroRequest getMacroMachine( )
-            throws PwmUnrecoverableException
-    {
-        final UserInfo userInfoBean = pwmSession.isAuthenticated()
-                ? pwmSession.getUserInfo()
-                : null;
-        return MacroRequest.forUser( pwmDomain.getPwmApplication(), pwmSession.getLabel(), userInfoBean, pwmSession.getLoginInfoBean() );
-    }
-}

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

@@ -165,7 +165,7 @@ public class AuthenticationFilter extends AbstractPwmFilter
                 LOGGER.info( pwmRequest, errorInformation );
 
                 // log out their user
-                pwmSession.unauthenticateUser( pwmRequest );
+                pwmSession.unAuthenticateUser( pwmRequest );
 
                 // send en error to user.
                 pwmRequest.respondWithError( errorInformation, true );
@@ -192,7 +192,7 @@ public class AuthenticationFilter extends AbstractPwmFilter
             return;
         }
 
-        if ( pwmSession.getSessionManager().isAuthenticatedWithoutPasswordAndBind() )
+        if ( pwmRequest.getClientConnectionHolder().isAuthenticatedWithoutPasswordAndBind() )
         {
             final Optional<PwmServletDefinition> pwmServletDefinition = pwmRequest.getURL().forServletDefinition();
             if ( pwmServletDefinition.isPresent() )

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

@@ -22,11 +22,9 @@ package password.pwm.http.filter;
 
 import password.pwm.Permission;
 import password.pwm.PwmApplicationMode;
-import password.pwm.PwmDomain;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.util.logging.PwmLogger;
@@ -58,15 +56,11 @@ public class AuthorizationFilter extends AbstractPwmFilter
     )
             throws IOException, ServletException, PwmUnrecoverableException
     {
-
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-
         // if the user is not authenticated as a PWM Admin, redirect to error page.
         boolean hasPermission = false;
         try
         {
-            hasPermission = pwmSession.getSessionManager().checkPermission( pwmDomain, Permission.PWMADMIN );
+            hasPermission = pwmRequest.checkPermission( Permission.PWMADMIN );
         }
         catch ( final Exception e )
         {

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

@@ -143,7 +143,7 @@ public class ConfigAccessFilter extends AbstractPwmFilter
                 throw new PwmUnrecoverableException( PwmError.ERROR_AUTHENTICATION_REQUIRED );
             }
 
-            if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN ) )
+            if ( !pwmRequest.checkPermission( Permission.PWMADMIN ) )
             {
                 throw new PwmUnrecoverableException( PwmError.ERROR_UNAUTHORIZED );
             }

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

@@ -170,15 +170,7 @@ public class RequestInitializationFilter implements Filter
             return;
         }
 
-        try
-        {
-            localPwmApplication.getActiveServletRequests().incrementAndGet();
-            initializeServletRequest( req, resp, filterChain );
-        }
-        finally
-        {
-            localPwmApplication.getActiveServletRequests().decrementAndGet();
-        }
+        initializeServletRequest( req, resp, filterChain );
     }
 
     private void initializeServletRequest(
@@ -240,6 +232,16 @@ public class RequestInitializationFilter implements Filter
                 return;
             }
 
+
+            try
+            {
+                pwmRequest.getPwmDomain().getActiveServletRequests().incrementAndGet();
+                filterChain.doFilter( req, resp );
+            }
+            finally
+            {
+                pwmRequest.getPwmDomain().getActiveServletRequests().decrementAndGet();
+            }
         }
         catch ( final Throwable e )
         {
@@ -266,8 +268,6 @@ public class RequestInitializationFilter implements Filter
             }
             return;
         }
-
-        filterChain.doFilter( req, resp );
     }
 
     private void updateStats( final PwmApplication localPwmApplication )
@@ -579,7 +579,7 @@ public class RequestInitializationFilter implements Filter
         }
 
         // set idle timeout
-        PwmSessionFactory.setHttpSessionIdleTimeout( pwmRequest.getPwmDomain(), pwmRequest.getPwmSession(), pwmRequest.getHttpServletRequest().getSession() );
+        PwmSessionFactory.setHttpSessionIdleTimeout( pwmRequest.getPwmDomain(), pwmRequest, pwmRequest.getHttpServletRequest().getSession() );
     }
 
     private static void initializeLocaleAndTheme(
@@ -818,7 +818,7 @@ public class RequestInitializationFilter implements Filter
         {
             LOGGER.debug( () -> "unauthenticated session due to idle time, max for request is " + maxDurationForRequest.asCompactString()
                     + ", session idle time is " + currentDuration.asCompactString() );
-            pwmRequest.getPwmSession().unauthenticateUser( pwmRequest );
+            pwmRequest.getPwmSession().unAuthenticateUser( pwmRequest );
         }
     }
 

+ 1 - 0
server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java

@@ -262,6 +262,7 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
         {
             final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest );
             pwmRequest.outputJsonResult( restResultBean );
+            pwmRequest.getPwmResponse().setStatus( 500 );
         }
         else
         {

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

@@ -378,7 +378,7 @@ public class ClientApiServlet extends ControlledPwmServlet
                 );
                 if ( StringUtil.notEmpty( configuredGuideText ) )
                 {
-                    final MacroRequest macroRequest = pwmSession.getSessionManager().getMacroMachine();
+                    final MacroRequest macroRequest = pwmRequest.getMacroMachine();
                     final String expandedText = macroRequest.expandMacros( configuredGuideText );
                     settingMap.put( "passwordGuideText", expandedText );
                 }
@@ -452,7 +452,7 @@ public class ClientApiServlet extends ControlledPwmServlet
         final ResourceBundle bundle = ResourceBundle.getBundle( displayClass.getName() );
         try
         {
-            final MacroRequest macroRequest = pwmSession.getSessionManager().getMacroMachine( );
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine();
             for ( final String key : new TreeSet<>( Collections.list( bundle.getKeys() ) ) )
             {
                 String displayValue = LocaleHelper.getLocalizedMessage( userLocale, key, config, displayClass );
@@ -534,7 +534,7 @@ public class ClientApiServlet extends ControlledPwmServlet
                 throw new PwmUnrecoverableException( errorInformation );
             }
 
-            if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN ) )
+            if ( !pwmRequest.checkPermission( Permission.PWMADMIN ) )
             {
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, "admin privileges required" );
                 throw new PwmUnrecoverableException( errorInformation );

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

@@ -145,7 +145,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
         {
             if ( !bean.isAgreementPassed() )
             {
-                final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
                 final String expandedText = macroRequest.expandMacros( selfDeleteAgreementText );
                 pwmRequest.setAttribute( PwmRequestAttribute.AgreementText, expandedText );
                 pwmRequest.forwardToJsp( JspUrl.SELF_DELETE_AGREE );
@@ -211,7 +211,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
 
                 final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmDomain, userIdentity )
                         .setExpandPwmMacros( true )
-                        .setMacroMachine( pwmRequest.getPwmSession().getSessionManager().getMacroMachine( ) )
+                        .setMacroMachine( pwmRequest.getMacroMachine( ) )
                         .createActionExecutor();
 
                 try
@@ -235,7 +235,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
         final String nextUrl = deleteAccountProfile.readSettingAsString( PwmSetting.DELETE_ACCOUNT_NEXT_URL );
         if ( nextUrl != null && !nextUrl.isEmpty() )
         {
-            final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
             final String macroedUrl = macroRequest.expandMacros( nextUrl );
             LOGGER.debug( pwmRequest, () -> "setting forward url to post-delete next url: " + macroedUrl );
             pwmRequest.getPwmSession().getSessionStateBean().setForwardURL( macroedUrl );
@@ -261,7 +261,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
         pwmDomain.getSessionStateService().clearBean( pwmRequest, DeleteAccountBean.class );
 
         // delete finished, so logout and redirect.
-        pwmRequest.getPwmSession().unauthenticateUser( pwmRequest );
+        pwmRequest.getPwmSession().unAuthenticateUser( pwmRequest );
         pwmRequest.getPwmResponse().sendRedirectToContinue();
         return ProcessStatus.Halt;
     }
@@ -284,7 +284,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
         pwmRequest.getPwmApplication().getEmailQueue().submitEmail(
                 configuredEmailSetting,
                 pwmRequest.getPwmSession().getUserInfo(),
-                pwmRequest.getPwmSession().getSessionManager().getMacroMachine( )
+                pwmRequest.getMacroMachine( )
         );
     }
 }

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

@@ -87,7 +87,7 @@ public class FullPageHealthServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException, IOException, ServletException
     {
         forwardToJSP( pwmRequest );
-        pwmRequest.getPwmSession().unauthenticateUser( pwmRequest );
+        pwmRequest.getPwmSession().unAuthenticateUser( pwmRequest );
     }
 
     @Override

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

@@ -147,7 +147,7 @@ public class GuestRegistrationServlet extends ControlledPwmServlet
             );
         }
 
-        if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmDomain, Permission.GUEST_REGISTRATION ) )
+        if ( !pwmRequest.checkPermission( Permission.GUEST_REGISTRATION ) )
         {
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_UNAUTHORIZED ) );
         }
@@ -221,7 +221,7 @@ public class GuestRegistrationServlet extends ControlledPwmServlet
             FormUtility.validateFormValues( config, formValues, ssBean.getLocale() );
 
             //read current values from user.
-            final ChaiUser theGuest = pwmSession.getSessionManager().getActor( guestRegistrationBean.getUpdateUserIdentity() );
+            final ChaiUser theGuest = pwmRequest.getClientConnectionHolder().getActor( guestRegistrationBean.getUpdateUserIdentity() );
 
             // check unique fields against ldap
             FormUtility.validateFormValueUniqueness(
@@ -235,7 +235,7 @@ public class GuestRegistrationServlet extends ControlledPwmServlet
             final Instant expirationDate = readExpirationFromRequest( pwmRequest );
 
             // Update user attributes
-            LdapOperationsHelper.writeFormValuesToLdap( pwmSession.getLabel(), theGuest, formValues, pwmSession.getSessionManager().getMacroMachine( ), false );
+            LdapOperationsHelper.writeFormValuesToLdap( pwmRequest.getLabel(), theGuest, formValues, pwmRequest.getMacroMachine( ), false );
 
             // Write expirationDate
             if ( expirationDate != null )
@@ -302,7 +302,7 @@ public class GuestRegistrationServlet extends ControlledPwmServlet
         LOGGER.trace( pwmRequest, () -> "Enter: handleSearchRequest(...)" );
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final ChaiProvider chaiProvider = pwmSession.getSessionManager().getChaiProvider();
+        final ChaiProvider chaiProvider = pwmRequest.getClientConnectionHolder().getActorChaiProvider();
         final DomainConfig config = pwmDomain.getConfig();
 
         final String adminDnAttribute = config.readSettingAsString( PwmSetting.GUEST_ADMIN_ATTRIBUTE );
@@ -340,7 +340,7 @@ public class GuestRegistrationServlet extends ControlledPwmServlet
                         pwmRequest.getLabel(),
                         pwmRequest.getLocale(),
                         theGuest,
-                        pwmSession.getSessionManager().getChaiProvider()
+                        pwmRequest.getClientConnectionHolder().getActorChaiProvider()
                 );
                 final Map<String, String> userAttrValues = guestUserInfo.readStringAttributes( involvedAttrs );
                 if ( origAdminOnly && adminDnAttribute != null && adminDnAttribute.length() > 0 )
@@ -428,7 +428,7 @@ public class GuestRegistrationServlet extends ControlledPwmServlet
             final String guestUserDN = determineUserDN( formValues, config );
 
             // read a chai provider to make the user
-            final ChaiProvider provider = pwmSession.getSessionManager().getChaiProvider();
+            final ChaiProvider provider = pwmRequest.getClientConnectionHolder().getActorChaiProvider();
 
             // set up the user creation attributes
             final Map<String, String> createAttributes = new HashMap<>();

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

@@ -131,7 +131,7 @@ public class LogoutServlet extends ControlledPwmServlet
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
 
-        pwmSession.unauthenticateUser( pwmRequest );
+        pwmSession.unAuthenticateUser( pwmRequest );
 
         {
             //if there is a session url, then use that to do a redirect.

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

@@ -329,7 +329,7 @@ public class SetupOtpServlet extends ControlledPwmServlet
         final UserIdentity theUser = pwmSession.getUserInfo().getUserIdentity();
         try
         {
-            service.clearOTPUserConfiguration( pwmRequest, theUser, pwmSession.getSessionManager().getActor( ) );
+            service.clearOTPUserConfiguration( pwmRequest, theUser, pwmRequest.getClientConnectionHolder().getActor( ) );
         }
         catch ( final PwmOperationalException e )
         {
@@ -441,7 +441,7 @@ public class SetupOtpServlet extends ControlledPwmServlet
                 final DomainConfig config = pwmDomain.getConfig();
                 final SetupOtpProfile setupOtpProfile = getSetupOtpProfile( pwmRequest );
                 final String identifierConfigValue = setupOtpProfile.readSettingAsString( PwmSetting.OTP_SECRET_IDENTIFIER );
-                final String identifier = pwmSession.getSessionManager().getMacroMachine( ).expandMacros( identifierConfigValue );
+                final String identifier = pwmRequest.getMacroMachine( ).expandMacros( identifierConfigValue );
                 final OTPUserRecord otpUserRecord = new OTPUserRecord();
                 final List<String> rawRecoveryCodes = pwmDomain.getOtpService().initializeUserRecord(
                         setupOtpProfile,
@@ -531,7 +531,7 @@ public class SetupOtpServlet extends ControlledPwmServlet
                 return true;
             }
 
-            final boolean admin = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN );
+            final boolean admin = pwmRequest.checkPermission( Permission.PWMADMIN );
             if ( admin )
             {
                 if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES ) )

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java

@@ -106,7 +106,7 @@ public class AccountInformationBean implements Serializable
             throws PwmUnrecoverableException
     {
         final PwmPasswordPolicy pwmPasswordPolicy = pwmRequest.getPwmSession().getUserInfo().getPasswordPolicy();
-        final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine();
+        final MacroRequest macroRequest = pwmRequest.getMacroMachine();
         final List<String> rules = PasswordRequirementsTag.getPasswordRequirementsStrings( pwmPasswordPolicy, pwmRequest.getDomainConfig(), pwmRequest.getLocale(), macroRequest );
         return Collections.unmodifiableList( rules );
     }

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java

@@ -252,7 +252,7 @@ class ActivateUserUtils
         pwmDomain.getPwmApplication().getEmailQueue().submitEmail(
                 configuredEmailSetting,
                 pwmSession.getUserInfo(),
-                pwmSession.getSessionManager().getMacroMachine( )
+                pwmRequest.getMacroMachine( )
         );
         return true;
     }
@@ -291,7 +291,7 @@ class ActivateUserUtils
                 toSmsNumber,
                 message,
                 pwmRequest.getLabel(),
-                pwmSession.getSessionManager().getMacroMachine( )
+                pwmRequest.getMacroMachine( )
         );
         return true;
     }

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

@@ -172,7 +172,7 @@ public class AdminServlet extends ControlledPwmServlet
             return ProcessStatus.Halt;
         }
 
-        if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN ) )
+        if ( !pwmRequest.checkPermission( Permission.PWMADMIN ) )
         {
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED );
             pwmRequest.respondWithError( errorInformation );
@@ -346,7 +346,7 @@ public class AdminServlet extends ControlledPwmServlet
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
-        if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN ) )
+        if ( !pwmRequest.checkPermission( Permission.PWMADMIN ) )
         {
             LOGGER.info( pwmRequest, () -> "unable to execute clear intruder records" );
             return ProcessStatus.Halt;

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java

@@ -209,7 +209,7 @@ public class AppDashboardData implements Serializable
 
         builder.ldapConnectionCount( LdapDomainService.totalLdapConnectionCount( pwmDomain.getPwmApplication() ) );
         builder.sessionCount( pwmDomain.getSessionTrackService().sessionCount() );
-        builder.requestsInProgress( pwmDomain.getPwmApplication().getActiveServletRequests().get() );
+        builder.requestsInProgress( pwmDomain.getPwmApplication().getTotalActiveServletRequests() );
 
         LOGGER.trace( () -> "AppDashboardData bean created", TimeDuration.fromCurrent( startTime ) );
         return builder.build();

+ 7 - 10
server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -148,7 +148,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         if ( pwmSession.getLoginInfoBean().getAuthFlags().contains( AuthenticationType.AUTH_FROM_PUBLIC_MODULE ) )
         {
             // Must have gotten here from the "Forgotten Password" link.  Better clear out the temporary authentication
-            pwmSession.unauthenticateUser( pwmRequest );
+            pwmSession.unAuthenticateUser( pwmRequest );
         }
 
         pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, ChangePasswordBean.class );
@@ -208,7 +208,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         // check the password meets the requirements
         try
         {
-            final ChaiUser theUser = pwmRequest.getPwmSession().getSessionManager().getActor( );
+            final ChaiUser theUser = pwmRequest.getClientConnectionHolder().getActor( );
             final PwmPasswordRuleValidator pwmPasswordRuleValidator = PwmPasswordRuleValidator.create(
                     pwmRequest.getLabel(), pwmRequest.getPwmDomain(), userInfo.getPasswordPolicy() );
             final PasswordData oldPassword = pwmRequest.getPwmSession().getLoginInfoBean().getUserCurrentPassword();
@@ -316,8 +316,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
             final Map<FormConfiguration, String> formValues = FormUtility.readFormValuesFromRequest(
                     pwmRequest, formItem, ssBean.getLocale() );
 
-            ChangePasswordServletUtil.validateParamsAgainstLDAP( formValues, pwmRequest,
-                    pwmSession.getSessionManager().getActor( ) );
+            ChangePasswordServletUtil.validateParamsAgainstLDAP( formValues, pwmRequest, pwmRequest.getClientConnectionHolder().getActor( ) );
 
             changePasswordBean.setFormPassed( true );
         }
@@ -406,7 +405,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
             pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, ChangePasswordBean.class );
             if ( completeMessage != null && !completeMessage.isEmpty() )
             {
-                final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
                 final String expandedText = macroRequest.expandMacros( completeMessage );
                 pwmRequest.setAttribute( PwmRequestAttribute.CompleteText, expandedText );
                 pwmRequest.forwardToJsp( JspUrl.PASSWORD_COMPLETE );
@@ -436,7 +435,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         final UserInfo userInfo = pwmSession.getUserInfo();
         final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword(
                 pwmRequest.getPwmRequestContext(),
-                pwmSession.getSessionManager().getActor(),
+                pwmRequest.getClientConnectionHolder().getActor(),
                 userInfo,
                 pwmSession.getLoginInfoBean(),
                 PasswordData.forStringValue( jsonInput.getPassword1() ),
@@ -474,8 +473,6 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException, ServletException
     {
         final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
-
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
         final ChangePasswordProfile changePasswordProfile = getProfile( pwmRequest );
 
         if ( changePasswordBean.getChangeProgressTracker() != null )
@@ -494,7 +491,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         final String agreementMsg = changePasswordProfile.readSettingAsLocalizedString( PwmSetting.PASSWORD_CHANGE_AGREEMENT_MESSAGE, pwmRequest.getLocale() );
         if ( StringUtil.notEmpty( agreementMsg ) && !changePasswordBean.isAgreementPassed() )
         {
-            final MacroRequest macroRequest = pwmSession.getSessionManager().getMacroMachine();
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine();
             final String expandedText = macroRequest.expandMacros( agreementMsg );
             pwmRequest.setAttribute( PwmRequestAttribute.AgreementText, expandedText );
             pwmRequest.forwardToJsp( JspUrl.PASSWORD_AGREEMENT );
@@ -591,7 +588,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         final Optional<String> passwordPolicyChangeMessage = pwmRequest.getPwmSession().getUserInfo().getPasswordPolicy().getChangeMessage( pwmRequest.getLocale() );
         if ( passwordPolicyChangeMessage.isPresent() )
         {
-            final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
             final String expandedMessage = macroRequest.expandMacros( passwordPolicyChangeMessage.get() );
             pwmRequest.setAttribute( PwmRequestAttribute.ChangePassword_PasswordPolicyChangeMessage, expandedMessage );
         }

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java

@@ -164,7 +164,7 @@ public class ChangePasswordServletUtil
                 configuredEmailSetting,
                 pwmRequest.getPwmSession().getUserInfo(),
 
-                pwmRequest.getPwmSession().getSessionManager().getMacroMachine( ) );
+                pwmRequest.getMacroMachine( ) );
     }
 
     static void checkMinimumLifetime(

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

@@ -326,6 +326,11 @@ public class NavTreeDataMaker
             }
         }
 
+        if ( category.isHidden() )
+        {
+            return false;
+        }
+
         for ( final PwmSettingCategory childCategory : category.getChildren() )
         {
             if ( categoryMatcher( domainID, childCategory, profile, storedConfiguration, navTreeSettings ) )

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

@@ -25,10 +25,9 @@ import org.apache.commons.csv.CSVPrinter;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.PwmConstants;
-import password.pwm.PwmDomain;
 import password.pwm.config.AppConfig;
-import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationFileManager;
+import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
@@ -47,7 +46,6 @@ import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmResponse;
-import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ConfigManagerBean;
 import password.pwm.http.filter.ConfigAccessFilter;
 import password.pwm.http.servlet.AbstractPwmServlet;
@@ -226,9 +224,6 @@ public class ConfigManagerServlet extends AbstractPwmServlet
     private void restLockConfiguration( final PwmRequest pwmRequest )
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-
         if ( PwmConstants.TRIAL_MODE )
         {
             final String msg = LocaleHelper.getLocalizedMessage( Admin.Notice_TrialRestrictConfig, pwmRequest );
@@ -239,8 +234,8 @@ public class ConfigManagerServlet extends AbstractPwmServlet
             return;
         }
 
-        if ( !pwmSession.isAuthenticated()
-                || !pwmSession.getSessionManager().checkPermission( pwmDomain, Permission.PWMADMIN ) )
+        if ( !pwmRequest.isAuthenticated()
+                || !pwmRequest.checkPermission( Permission.PWMADMIN ) )
         {
             final ErrorInformation errorInfo = new ErrorInformation(
                     PwmError.ERROR_AUTHENTICATION_REQUIRED,

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

@@ -1012,7 +1012,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         final String agreementMsg = forgottenPasswordProfile.readSettingAsLocalizedString( PwmSetting.RECOVERY_AGREEMENT_MESSAGE, pwmRequest.getLocale() );
         if ( StringUtil.notEmpty( agreementMsg ) && !forgottenPasswordBean.isAgreementPassed() )
         {
-            final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine();
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine();
             final String expandedText = macroRequest.expandMacros( agreementMsg );
             pwmRequest.setAttribute( PwmRequestAttribute.AgreementText, expandedText );
             pwmRequest.forwardToJsp( JspUrl.RECOVER_USER_AGREEMENT );

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

@@ -525,7 +525,7 @@ public class ForgottenPasswordUtil
             final PwmSession pwmSession = pwmRequest.getPwmSession();
 
             // the user should not be authenticated, this is a safety method
-            pwmSession.unauthenticateUser( pwmRequest );
+            pwmSession.unAuthenticateUser( pwmRequest );
 
             // the password set flag should not have been set, this is a safety method
             pwmSession.getSessionStateBean().setPasswordModified( false );

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

@@ -365,7 +365,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
         // execute user delete operation
         final ChaiProvider provider = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY )
                 ? pwmDomain.getProxyChaiProvider( pwmRequest.getLabel(), userIdentity.getLdapProfileID() )
-                : pwmSession.getSessionManager().getChaiProvider();
+                : pwmRequest.getClientConnectionHolder().getActor().getChaiProvider();
 
 
         try

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

@@ -330,7 +330,7 @@ public class HelpdeskServletUtil
         final boolean useProxy = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY );
         return useProxy
                 ? pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity )
-                : pwmRequest.getPwmSession().getSessionManager().getActor( userIdentity );
+                : pwmRequest.getClientConnectionHolder().getActor( userIdentity );
     }
 
     static UserInfo getTargetUserInfo(

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -788,7 +788,7 @@ public class NewUserServlet extends ControlledPwmServlet
         final String configuredRedirectUrl = newUserProfile.readSettingAsString( PwmSetting.NEWUSER_REDIRECT_URL );
         if ( StringUtil.notEmpty( configuredRedirectUrl ) && StringUtil.isEmpty( pwmRequest.getPwmSession().getSessionStateBean().getForwardURL() ) )
         {
-            final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine();
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine();
             final String macroedUrl = macroRequest.expandMacros( configuredRedirectUrl );
             pwmRequest.getPwmResponse().sendRedirect( macroedUrl );
             return ProcessStatus.Halt;

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

@@ -326,7 +326,7 @@ class NewUserUtils
 
                 final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmDomain, userIdentity )
                         .setExpandPwmMacros( true )
-                        .setMacroMachine( pwmRequest.getPwmSession().getSessionManager().getMacroMachine( ) )
+                        .setMacroMachine( pwmRequest.getMacroMachine( ) )
                         .createActionExecutor();
 
                 actionExecutor.executeActions( actions, pwmRequest.getLabel() );
@@ -365,7 +365,7 @@ class NewUserUtils
             NewUserUtils.LOGGER.error( pwmRequest, () -> "error deleting ldap user account " + userDN + ", " + e.getMessage() );
         }
 
-        pwmRequest.getPwmSession().unauthenticateUser( pwmRequest );
+        pwmRequest.getPwmSession().unAuthenticateUser( pwmRequest );
     }
 
     static String determineUserDN(
@@ -477,7 +477,7 @@ class NewUserUtils
         pwmRequest.getPwmDomain().getPwmApplication().getEmailQueue().submitEmail(
                 configuredEmailSetting,
                 pwmSession.getUserInfo(),
-                pwmSession.getSessionManager().getMacroMachine( )
+                pwmRequest.getMacroMachine( )
         );
     }
 

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

@@ -268,7 +268,7 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
                     final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, errorMsg );
                     LOGGER.error( pwmRequest, () -> errorMsg );
                     pwmRequest.respondWithError( errorInformation );
-                    pwmSession.unauthenticateUser( pwmRequest );
+                    pwmSession.unAuthenticateUser( pwmRequest );
                     return;
                 }
             }

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

@@ -289,7 +289,7 @@ public class OAuthMachine
                     .certificates( CollectionUtil.isEmpty( certs ) ? null : certs )
                     .maskBodyDebugOutput( true )
                     .build();
-            final PwmHttpClient pwmHttpClient = pwmRequest.getPwmDomain().getHttpClientService().getPwmHttpClient( config, pwmRequest.getLabel() );
+            final PwmHttpClient pwmHttpClient = pwmRequest.getClientConnectionHolder().getPwmHttpClient( config );
             pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest );
         }
         catch ( final PwmException e )
@@ -395,7 +395,7 @@ public class OAuthMachine
             LOGGER.error( sessionLabel, () -> "error while processing oauth token refresh: " + e.getMessage() );
         }
         LOGGER.error( sessionLabel, () -> "unable to refresh oauth token for user, unauthenticated session" );
-        pwmRequest.getPwmSession().unauthenticateUser( pwmRequest );
+        pwmRequest.getPwmSession().unAuthenticateUser( pwmRequest );
         return true;
     }
 

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

@@ -701,7 +701,7 @@ class PeopleSearchDataReader
         final boolean useProxy = useProxy();
         return useProxy
                 ? pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity )
-                : pwmRequest.getPwmSession().getSessionManager().getActor( userIdentity );
+                : pwmRequest.getClientConnectionHolder().getActor( userIdentity );
     }
 
     private UserSearchResults doDetailLookup(
@@ -840,7 +840,7 @@ class PeopleSearchDataReader
         if ( !useProxy() )
         {
             builder.ldapProfile( pwmRequest.getPwmSession().getUserInfo().getUserIdentity().getLdapProfileID() );
-            builder.chaiProvider( pwmRequest.getPwmSession().getSessionManager().getChaiProvider() );
+            builder.chaiProvider( pwmRequest.getClientConnectionHolder().getActorChaiProvider() );
         }
 
         final SearchRequestBean.SearchMode searchMode = searchRequest.getMode() == null

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

@@ -237,7 +237,7 @@ public class PhotoDataReader
             final PwmHttpClientConfiguration configuration = PwmHttpClientConfiguration.builder()
                     .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous )
                     .build();
-            final PwmHttpClient pwmHttpClient = pwmRequest.getPwmDomain().getHttpClientService().getPwmHttpClient( configuration, pwmRequest.getLabel() );
+            final PwmHttpClient pwmHttpClient = pwmRequest.getClientConnectionHolder().getPwmHttpClient( configuration );
             final PwmHttpClientRequest clientRequest = PwmHttpClientRequest.builder()
                     .method( HttpMethod.GET )
                     .url( overrideURL.get() )

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java

@@ -231,7 +231,7 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         try
         {
             final String userGUID = pwmSession.getUserInfo().getUserGuid();
-            final ChaiUser theUser = pwmSession.getSessionManager().getActor( );
+            final ChaiUser theUser = pwmRequest.getClientConnectionHolder().getActor( );
             pwmDomain.getCrService().clearResponses( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn(), theUser, userGUID );
             pwmSession.reloadUserInfoBean( pwmRequest );
             pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, SetupResponsesBean.class );
@@ -445,7 +445,7 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final ChaiUser theUser = pwmSession.getSessionManager().getActor( );
+        final ChaiUser theUser = pwmRequest.getClientConnectionHolder().getActor( );
         final String userGUID = pwmSession.getUserInfo().getUserGuid();
         pwmDomain.getCrService().writeResponses( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn(), theUser, userGUID, responseInfoBean );
         pwmSession.reloadUserInfoBean( pwmRequest );

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java

@@ -73,7 +73,7 @@ public class SetupResponsesUtil
     {
         if ( pwmRequest.isForcedPageView() )
         {
-            final boolean admin = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN );
+            final boolean admin = pwmRequest.checkPermission( Permission.PWMADMIN );
             if ( admin )
             {
                 if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES ) )
@@ -171,7 +171,7 @@ public class SetupResponsesUtil
     )
             throws PwmDataValidationException, PwmUnrecoverableException
     {
-        final ChaiProvider provider = pwmRequest.getPwmSession().getSessionManager().getChaiProvider();
+        final ChaiProvider provider = pwmRequest.getClientConnectionHolder().getActorChaiProvider();
 
         try
         {

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

@@ -359,7 +359,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet
             {
                 if ( !updateProfileBean.isAgreementPassed() )
                 {
-                    final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+                    final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
                     final String expandedText = macroRequest.expandMacros( updateProfileAgreementText );
                     pwmRequest.setAttribute( PwmRequestAttribute.AgreementText, expandedText );
                     pwmRequest.forwardToJsp( JspUrl.UPDATE_ATTRIBUTES_AGREEMENT );
@@ -424,13 +424,13 @@ public class UpdateProfileServlet extends ControlledPwmServlet
         try
         {
             // write the form values
-            final ChaiUser theUser = pwmSession.getSessionManager().getActor( );
+            final ChaiUser theUser = pwmRequest.getClientConnectionHolder().getActor( );
             UpdateProfileUtil.doProfileUpdate(
                     pwmRequest.getPwmDomain(),
                     pwmRequest.getLabel(),
                     pwmRequest.getLocale(),
                     pwmSession.getUserInfo(),
-                    pwmSession.getSessionManager().getMacroMachine( ),
+                    pwmRequest.getMacroMachine( ),
                     updateProfileProfile,
                     updateProfileBean.getFormData(),
                     theUser

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

@@ -134,7 +134,7 @@ public class DisplayTag extends PwmAbstractTag
 
             if ( pwmRequest != null )
             {
-                final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
                 displayMessage = macroRequest.expandMacros( displayMessage );
             }
 

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

@@ -88,7 +88,7 @@ public class ErrorMessageTag extends PwmAbstractTag
 
                 outputMsg = outputMsg.replace( "\n", "<br/>" );
 
-                final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
                 outputMsg = macroRequest.expandMacros( outputMsg );
 
                 pageContext.getOut().write( outputMsg );

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

@@ -575,7 +575,7 @@ public class PasswordRequirementsTag extends TagSupport
             final DomainConfig config = pwmDomain.getConfig();
             final Locale locale = pwmSession.getSessionStateBean().getLocale();
 
-            pwmSession.getSessionManager().getMacroMachine( );
+            pwmRequest.getMacroMachine( );
 
             final PwmPasswordPolicy passwordPolicy;
             if ( getForm() != null && "newuser".equalsIgnoreCase( getForm() ) )
@@ -595,7 +595,7 @@ public class PasswordRequirementsTag extends TagSupport
             }
             else
             {
-                final MacroRequest macroRequest = pwmSession.getSessionManager().getMacroMachine( );
+                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
 
                 final String pre = prepend != null && prepend.length() > 0 ? prepend : "";
                 final String sep = separator != null && separator.length() > 0 ? separator : "<br/>";

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

@@ -53,7 +53,7 @@ public class PwmMacroTag extends TagSupport
         try
         {
             final PwmRequest pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() );
-            final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
             final String outputValue = macroRequest.expandMacros( value );
             pageContext.getOut().write( outputValue );
         }

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

@@ -55,7 +55,7 @@ public class SuccessMessageTag extends PwmAbstractTag
             {
                 if ( pwmRequest.isAuthenticated() )
                 {
-                    final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+                    final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
                     outputMsg = macroRequest.expandMacros( successMsg );
                 }
                 else

+ 4 - 7
server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -196,7 +196,7 @@ public enum PwmIfTest
         {
             final PwmApplicationMode applicationMode = pwmRequest.getPwmDomain().getApplicationMode();
             final boolean configMode = applicationMode == PwmApplicationMode.CONFIGURATION;
-            final boolean adminUser = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN );
+            final boolean adminUser = pwmRequest.checkPermission( Permission.PWMADMIN );
             if ( Boolean.parseBoolean( pwmRequest.getDomainConfig().readAppProperty( AppProperty.CLIENT_WARNING_HEADER_SHOW ) ) )
             {
                 if ( configMode || PwmConstants.TRIAL_MODE )
@@ -244,10 +244,7 @@ public enum PwmIfTest
                 return false;
             }
 
-            return pwmRequest != null
-                    && pwmRequest.getPwmSession().getSessionManager().checkPermission(
-                    pwmRequest.getPwmDomain(),
-                    permission );
+            return pwmRequest != null && pwmRequest.checkPermission( permission );
         }
     }
 
@@ -363,7 +360,7 @@ public enum PwmIfTest
                 return true;
             }
 
-            final boolean adminUser = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN );
+            final boolean adminUser = pwmRequest.checkPermission( Permission.PWMADMIN );
             if ( adminUser )
             {
                 final HealthService healthService = pwmRequest.getPwmApplication().getHealthMonitor();
@@ -407,7 +404,7 @@ public enum PwmIfTest
 
             if ( pwmRequest.isAuthenticated() )
             {
-                if ( pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN ) )
+                if ( pwmRequest.checkPermission( Permission.PWMADMIN ) )
                 {
                     return true;
                 }

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

@@ -110,7 +110,7 @@ public enum PwmValue
             {
                 try
                 {
-                    final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine();
+                    final MacroRequest macroRequest = pwmRequest.getMacroMachine();
                     outputURL = macroRequest.expandMacros( outputURL );
                 }
                 catch ( final PwmUnrecoverableException e )
@@ -153,7 +153,7 @@ public enum PwmValue
             {
                 try
                 {
-                    final MacroRequest macroRequest = pwmRequest.getPwmSession().getSessionManager().getMacroMachine();
+                    final MacroRequest macroRequest = pwmRequest.getMacroMachine();
                     return macroRequest.expandMacros( customScript );
                 }
                 catch ( final Exception e )
@@ -215,7 +215,7 @@ public enum PwmValue
                 }
                 return output + LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_ConfigModeActive", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
             }
-            else if ( pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN ) )
+            else if ( pwmRequest.checkPermission( Permission.PWMADMIN ) )
             {
                 return LocaleHelper.getLocalizedMessage( pwmRequest.getLocale(), "Header_AdminUser", pwmRequest.getDomainConfig(), Admin.class, fieldNames );
             }

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

@@ -25,7 +25,6 @@ import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.provider.ProviderStatistics;
 import lombok.Builder;
-import lombok.Data;
 import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -53,26 +52,21 @@ import java.io.Serializable;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Consumer;
 
 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 ThreadLocal<ThreadLocalContainer> threadLocalProvider = new ThreadLocal<>();
-    private final Set<ThreadLocalContainer> threadLocalContainers = Collections.synchronizedSet( Collections.newSetFromMap( new WeakHashMap<>() ) );
     private final ReentrantLock reentrantLock = new ReentrantLock();
     private final ConditionalTaskExecutor debugLogger = ConditionalTaskExecutor.forPeriodicTask(
             this::conditionallyLogDebugInfo,
@@ -83,8 +77,6 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
     private ChaiProviderFactory chaiProviderFactory;
     private AtomicLoopIntIncrementer slotIncrementer;
 
-    private boolean useThreadLocal;
-
     private final StatisticCounterBundle<StatKey> stats = new StatisticCounterBundle<>( StatKey.class );
 
     public static long totalLdapConnectionCount( final PwmApplication pwmApplication )
@@ -141,9 +133,6 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
         this.pwmDomain = pwmApplication.domains().get( domainID );
         this.chaiProviderFactory = ChaiProviderFactory.newProviderFactory();
 
-        useThreadLocal = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PROXY_USE_THREAD_LOCAL ) );
-        LOGGER.trace( getSessionLabel(), () -> "threadLocal enabled: " + useThreadLocal );
-
         // read the lastLoginTime
         this.lastLdapErrors.putAll( pwmApplication.readLastLdapFailure( getDomainID() ) );
 
@@ -177,8 +166,6 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
 
         proxyChaiProviders.clear();
         lastLdapErrors.clear();
-        iterateThreadLocals( container -> container.getProviderMap().clear() );
-        threadLocalContainers.clear();
     }
 
     @Override
@@ -199,7 +186,6 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
                 .build();
     }
 
-
     public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final String identifier )
             throws PwmUnrecoverableException
     {
@@ -221,11 +207,6 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
                 ? pwmDomain.getConfig().getDefaultLdapProfile()
                 : ldapProfile;
 
-        if ( useThreadLocal )
-        {
-            return getThreadLocalChaiProvider( sessionLabel, effectiveProfile );
-        }
-
         return getSharedLocalChaiProvider( sessionLabel, effectiveProfile );
     }
 
@@ -245,38 +226,6 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
         return proxyChaiProvider;
     }
 
-    private ChaiProvider getThreadLocalChaiProvider( final SessionLabel sessionLabel, final LdapProfile ldapProfile )
-            throws PwmUnrecoverableException
-    {
-        reentrantLock.lock();
-        try
-        {
-            if ( threadLocalProvider.get() == null )
-            {
-                final ThreadLocalContainer threadLocalContainer = new ThreadLocalContainer();
-                threadLocalProvider.set( threadLocalContainer );
-                threadLocalContainers.add( threadLocalContainer );
-            }
-
-            final String profileID = ldapProfile.getIdentifier();
-            final ThreadLocalContainer threadLocalContainer = threadLocalProvider.get();
-
-            if ( !threadLocalContainer.getProviderMap().containsKey( profileID ) )
-            {
-                final ChaiProvider chaiProvider = newProxyChaiProvider( sessionLabel, ldapProfile );
-                threadLocalContainer.getProviderMap().put( profileID, chaiProvider );
-            }
-
-            threadLocalContainer.setTimestamp( Instant.now() );
-            threadLocalContainer.setThreadName( Thread.currentThread().getName() );
-            return threadLocalContainer.getProviderMap().get( profileID );
-        }
-        finally
-        {
-            reentrantLock.unlock();
-        }
-    }
-
     private ChaiProvider newProxyChaiProvider( final SessionLabel sessionLabel, final LdapProfile ldapProfile )
             throws PwmUnrecoverableException
     {
@@ -423,7 +372,6 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
         final int activeConnections = connectionCount();
 
         final AtomicInteger threadLocalConnections = new AtomicInteger( 0 );
-        iterateThreadLocals( container -> threadLocalConnections.set( threadLocalConnections.intValue() + container.getProviderMap().size() ) );
 
         final Map<DebugKey, String> debugInfo = new TreeMap<>();
         debugInfo.put( DebugKey.Allocated, String.valueOf( allocatedConnections ) );
@@ -434,53 +382,4 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
         return CollectionUtil.enumMapToStringMap( debugInfo );
     }
 
-    @Data
-    private static class ThreadLocalContainer
-    {
-        private final Map<String, ChaiProvider> providerMap = new ConcurrentHashMap<>();
-        private volatile Instant timestamp = Instant.now();
-        private volatile String threadName;
-    }
-
-    void cleanupIssuedThreadLocals()
-    {
-        final TimeDuration maxIdleTime = TimeDuration.MINUTE;
-
-        iterateThreadLocals( container ->
-        {
-            if ( !container.getProviderMap().isEmpty() )
-            {
-                final Instant timestamp = container.getTimestamp();
-                final TimeDuration age = TimeDuration.fromCurrent( timestamp );
-                if ( age.isLongerThan( maxIdleTime ) )
-                {
-                    for ( final ChaiProvider chaiProvider : container.getProviderMap().values() )
-                    {
-                        LOGGER.trace( getSessionLabel(), () -> "discarding idled connection id=" + chaiProvider.toString() + " from orphaned threadLocal, age="
-                                + age.asCompactString() + ", thread=" + container.getThreadName() );
-                        stats.increment( StatKey.clearedThreadLocals );
-                    }
-                    container.getProviderMap().clear();
-                }
-            }
-        } );
-
-        debugLogger.conditionallyExecuteTask();
-    }
-
-    private void iterateThreadLocals( final Consumer<ThreadLocalContainer> consumer )
-    {
-        reentrantLock.lock();
-        try
-        {
-            for ( final ThreadLocalContainer container : new HashSet<>( threadLocalContainers ) )
-            {
-                consumer.accept( container );
-            }
-        }
-        finally
-        {
-            reentrantLock.unlock();
-        }
-    }
 }

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

@@ -20,16 +20,12 @@
 
 package password.pwm.ldap;
 
-import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
-import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
 import password.pwm.error.PwmException;
 import password.pwm.health.HealthRecord;
 import password.pwm.svc.AbstractPwmService;
 import password.pwm.svc.PwmService;
-import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.TimeDuration;
 
 import java.util.Collections;
 import java.util.List;
@@ -39,12 +35,15 @@ public class LdapSystemService extends AbstractPwmService implements PwmService
     @Override
     protected STATUS postAbstractInit( final PwmApplication pwmApplication, final DomainID domainID ) throws PwmException
     {
+        /*
         final long idleWeakTimeoutMS = JavaHelper.silentParseLong(
                 pwmApplication.getConfig().readAppProperty( AppProperty.LDAP_PROXY_IDLE_THREAD_LOCAL_TIMEOUT_MS ),
                 60_000 );
         final TimeDuration idleWeakTimeout = TimeDuration.of( idleWeakTimeoutMS, TimeDuration.Unit.MILLISECONDS );
         pwmApplication.getPwmScheduler().scheduleFixedRateJob( new ThreadLocalCleaner(), getExecutorService(), idleWeakTimeout, idleWeakTimeout );
 
+         */
+
         return STATUS.OPEN;
     }
 
@@ -65,17 +64,4 @@ public class LdapSystemService extends AbstractPwmService implements PwmService
     {
         return null;
     }
-
-    private class ThreadLocalCleaner implements Runnable
-    {
-        @Override
-        public void run()
-        {
-            for ( final PwmDomain pwmDomain : getPwmApplication().domains().values() )
-            {
-                pwmDomain.getLdapConnectionService().cleanupIssuedThreadLocals();
-            }
-        }
-    }
-
 }

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

@@ -371,7 +371,7 @@ public class SessionAuthenticator
         loginInfoBean.setUserIdentity( userIdentity );
 
         //update the session connection
-        pwmSession.getSessionManager().setChaiProvider( authenticationResult.getUserProvider() );
+        pwmSession.updateLdapAuthentication( pwmRequest.getPwmApplication(), userIdentity, authenticationResult );
 
         // update the actor user info bean
         {

+ 5 - 6
server/src/main/java/password/pwm/svc/email/EmailConnectionPool.java

@@ -111,7 +111,7 @@ public class EmailConnectionPool
             }
             final Instant startTime = Instant.now();
             final EmailConnection emailConnection = getSmtpTransport();
-            LOGGER.trace( sessionLabel, () -> "created new email connection " + emailConnection.getId()
+            LOGGER.trace( sessionLabel, () -> "created new email connection #" + emailConnection.getId()
                             + " to " + emailConnection.getEmailServer().getId(),
                     TimeDuration.fromCurrent( startTime ) );
             activeConnectionCounter.incrementAndGet();
@@ -128,14 +128,13 @@ public class EmailConnectionPool
         lock.lock();
         try
         {
-
             if ( connections.add( emailConnection ) )
             {
                 activeConnectionCounter.decrementAndGet();
             }
             else
             {
-                LOGGER.warn( sessionLabel, () -> "connection " + emailConnection.getId() + "returned but was already in oool" );
+                LOGGER.warn( sessionLabel, () -> "connection #" + emailConnection.getId() + "returned but was already in oool" );
             }
         }
         finally
@@ -148,7 +147,7 @@ public class EmailConnectionPool
     {
         if ( emailConnection.getSentItems() >= settings.getConnectionSendItemLimit() )
         {
-            LOGGER.trace( sessionLabel, () -> "email connection " + emailConnection.getId()
+            LOGGER.trace( sessionLabel, () -> "email connection #" + emailConnection.getId()
                     + " has sent " + emailConnection.getSentItems() + " and will be retired" );
             return false;
         }
@@ -156,14 +155,14 @@ public class EmailConnectionPool
         final TimeDuration connectionAge = TimeDuration.fromCurrent( emailConnection.getStartTime() );
         if ( connectionAge.isLongerThan( settings.getConnectionSendItemDuration() ) )
         {
-            LOGGER.trace( sessionLabel, () -> "email connection " + emailConnection.getId()
+            LOGGER.trace( sessionLabel, () -> "email connection #" + emailConnection.getId()
                     + " has lived " + connectionAge.asCompactString() + " and will be retired" );
             return false;
         }
 
         if ( !emailConnection.getTransport().isConnected() )
         {
-            LOGGER.trace( sessionLabel, () -> "email connection " + emailConnection.getId()
+            LOGGER.trace( sessionLabel, () -> "email connection #" + emailConnection.getId()
                     + " is no longer connected " + connectionAge.asCompactString() + " and will be retired" );
             return false;
 

+ 1 - 1
server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java

@@ -79,7 +79,7 @@ public class AuditRecordFactory
                 pwmRequest.getLabel(),
                 pwmRequest.getDomainID(),
                 pwmRequest.getPwmApplication(),
-                pwmRequest.getPwmSession().getSessionManager().getMacroMachine( ) );
+                pwmRequest.getMacroMachine() );
     }
 
     public static AuditRecordFactory make( final SessionLabel sessionLabel, final PwmDomain pwmDomain, final MacroRequest macroRequest )

+ 1 - 1
server/src/main/java/password/pwm/svc/event/AuditServiceClient.java

@@ -62,7 +62,7 @@ public class AuditServiceClient
             throws PwmUnrecoverableException
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmApplication().domains().get( userInfo.getUserIdentity().getDomainID() );
-        final MacroRequest macroRequest =  pwmRequest.getPwmSession().getSessionManager().getMacroMachine( );
+        final MacroRequest macroRequest =  pwmRequest.getMacroMachine();
         final AuditRecordFactory auditRecordFactory = AuditRecordFactory.make( pwmRequest.getLabel(), pwmDomain, macroRequest );
         final UserAuditRecord auditRecord = auditRecordFactory.createUserAuditRecord( auditEvent, userInfo, pwmRequest.getPwmSession() );
         submit( pwmRequest, auditRecord );

+ 23 - 23
server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java

@@ -40,10 +40,8 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
 
 public class HttpClientService extends AbstractPwmService implements PwmService
 {
@@ -51,8 +49,7 @@ public class HttpClientService extends AbstractPwmService implements PwmService
 
     private Class<PwmHttpClientProvider> httpClientClass;
 
-    private final Map<PwmHttpClientConfiguration, ThreadLocal<PwmHttpClientProvider>> clients = new ConcurrentHashMap<>(  );
-    private final Map<PwmHttpClientProvider, Object> issuedClients = Collections.synchronizedMap( new WeakHashMap<>(  ) );
+    private final Set<PwmHttpClient> issuedClients = Collections.synchronizedSet( Collections.newSetFromMap( new WeakHashMap<>() ) );
 
     private final StatisticCounterBundle<StatsKey> stats = new StatisticCounterBundle<>( StatsKey.class );
 
@@ -62,7 +59,6 @@ public class HttpClientService extends AbstractPwmService implements PwmService
         requestBytes,
         responseBytes,
         createdClients,
-        reusedClients,
     }
 
     public HttpClientService()
@@ -98,7 +94,7 @@ public class HttpClientService extends AbstractPwmService implements PwmService
     @Override
     public void shutdownImpl()
     {
-        for ( final PwmHttpClient pwmHttpClient : new HashSet<>( issuedClients.keySet() ) )
+        for ( final PwmHttpClient pwmHttpClient : new HashSet<>( issuedClients ) )
         {
             try
             {
@@ -114,31 +110,28 @@ public class HttpClientService extends AbstractPwmService implements PwmService
     public PwmHttpClient getPwmHttpClient( final SessionLabel sessionLabel )
             throws PwmUnrecoverableException
     {
-        return this.getPwmHttpClient( PwmHttpClientConfiguration.builder().build(), sessionLabel );
+        return this.getPwmHttpClient( null, sessionLabel );
     }
 
-    public PwmHttpClient getPwmHttpClient( final PwmHttpClientConfiguration pwmHttpClientConfiguration, final SessionLabel sessionLabel )
+    public PwmHttpClient getPwmHttpClient(
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration,
+            final SessionLabel sessionLabel
+    )
             throws PwmUnrecoverableException
     {
-        Objects.requireNonNull( pwmHttpClientConfiguration );
-
-        final ThreadLocal<PwmHttpClientProvider> threadLocal = clients.computeIfAbsent(
-                pwmHttpClientConfiguration,
-                clientConfig -> new ThreadLocal<>() );
-
-        final PwmHttpClient existingClient = threadLocal.get();
-        if ( existingClient != null && existingClient.isOpen() )
+        if ( status() != STATUS.OPEN )
         {
-            stats.increment( StatsKey.reusedClients );
-            return existingClient;
+            throw new PwmUnrecoverableException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "unable to create new pwmHttpClient, service is closed" );
         }
 
+        final PwmHttpClientConfiguration effectiveConfig = pwmHttpClientConfiguration == null
+                ? PwmHttpClientConfiguration.builder().build()
+                : pwmHttpClientConfiguration;
+
         try
         {
             final PwmHttpClientProvider newClient = httpClientClass.getDeclaredConstructor().newInstance();
-            newClient.init( getPwmApplication(), this, pwmHttpClientConfiguration, sessionLabel );
-            issuedClients.put( newClient, null );
-            threadLocal.set( newClient );
+            newClient.init( getPwmApplication(), this, effectiveConfig, sessionLabel );
             stats.increment( StatsKey.createdClients );
             return newClient;
         }
@@ -163,11 +156,18 @@ public class HttpClientService extends AbstractPwmService implements PwmService
     public ServiceInfoBean serviceInfo()
     {
         final Map<String, String> debugMap = new HashMap<>( stats.debugStats() );
-        debugMap.put( "weakReferences", Integer.toString( issuedClients.size() ) );
-        debugMap.put( "referencedConfigs", Integer.toString( clients.size() ) );
+        debugMap.put( "issuedClients", Integer.toString( issuedClients.size() ) );
+        debugMap.put( "openClients", Long.toString( openClients() ) );
         return ServiceInfoBean.builder()
                 .debugProperties( debugMap )
                 .build();
 
     }
+
+    private long openClients()
+    {
+        return new HashSet<>( issuedClients ).stream()
+                .filter( PwmHttpClient::isOpen )
+                .count();
+    }
 }

+ 1 - 1
server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java

@@ -130,7 +130,7 @@ public class LdapOtpOperator extends AbstractOtpOperator
             }
             final ChaiUser theUser = pwmRequest == null
                     ? pwmDomain.getProxiedChaiUser( null, userIdentity )
-                    : pwmRequest.getPwmSession().getSessionManager().getActor( userIdentity );
+                    : pwmRequest.getClientConnectionHolder().getActor( userIdentity );
             theUser.writeStringAttribute( ldapStorageAttribute, value );
             LOGGER.info( () -> "saved OTP secret for user to chai-ldap format" );
         }

+ 23 - 21
server/src/main/java/password/pwm/svc/sms/SmsQueueService.java

@@ -233,7 +233,7 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
         }
     }
 
-    SmsItemBean shortenMessageIfNeeded(
+    private SmsItemBean shortenMessageIfNeeded(
             final SmsItemBean smsItem,
             final SessionLabel sessionLabel
     )
@@ -249,7 +249,7 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
         return smsItem;
     }
 
-    boolean determineIfItemCanBeDelivered( final SmsItemBean smsItem )
+    private boolean determineIfItemCanBeDelivered( final SmsItemBean smsItem )
     {
         if ( !getPwmApplication().getConfig().isSmsConfigured() )
         {
@@ -329,7 +329,7 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
     }
 
 
-    protected static String smsDataEncode( final String data, final SmsDataEncoding encoding )
+    private static String smsDataEncode( final String data, final SmsDataEncoding encoding )
     {
         final String normalizedString = data == null ? "" : data;
 
@@ -413,7 +413,7 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
         ) );
     }
 
-    static String formatSmsNumber( final AppConfig config, final String smsNumber )
+    private static String formatSmsNumber( final AppConfig config, final String smsNumber )
     {
         final SmsNumberFormat format = config.readSettingAsEnum( PwmSetting.SMS_PHONE_NUMBER_FORMAT, SmsNumberFormat.class );
 
@@ -509,25 +509,9 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
 
             final PwmHttpClientRequest pwmHttpClientRequest = makeRequest( requestData, sessionLabel );
 
-            final PwmHttpClient pwmHttpClient;
-            {
-                if ( CollectionUtil.isEmpty( config.readSettingAsCertificate( PwmSetting.SMS_GATEWAY_CERTIFICATES ) ) )
-                {
-                    pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( sessionLabel );
-                }
-                else
-                {
-                    final PwmHttpClientConfiguration clientConfiguration = PwmHttpClientConfiguration.builder()
-                            .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
-                            .certificates( config.readSettingAsCertificate( PwmSetting.SMS_GATEWAY_CERTIFICATES ) )
-                            .build();
-
-                    pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( clientConfiguration, sessionLabel );
-                }
-            }
-
             try
             {
+                final PwmHttpClient pwmHttpClient = makePwmHttpClient( sessionLabel );
                 final PwmHttpClientResponse pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest );
                 final int resultCode = pwmHttpClientResponse.getStatusCode();
 
@@ -546,6 +530,24 @@ public class SmsQueueService extends AbstractPwmService implements PwmService
             }
         }
 
+        private PwmHttpClient makePwmHttpClient( final SessionLabel sessionLabel )
+                throws PwmUnrecoverableException
+        {
+            if ( CollectionUtil.isEmpty( config.readSettingAsCertificate( PwmSetting.SMS_GATEWAY_CERTIFICATES ) ) )
+            {
+                return pwmApplication.getHttpClientService().getPwmHttpClient( sessionLabel );
+            }
+            else
+            {
+                final PwmHttpClientConfiguration clientConfiguration = PwmHttpClientConfiguration.builder()
+                        .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
+                        .certificates( config.readSettingAsCertificate( PwmSetting.SMS_GATEWAY_CERTIFICATES ) )
+                        .build();
+
+                return pwmApplication.getHttpClientService().getPwmHttpClient( clientConfiguration, sessionLabel );
+            }
+        }
+
         private String makeRequestData(
                 final String to,
                 final String message,

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

@@ -167,7 +167,7 @@ public class CaptchaUtility
                     .build();
 
             LOGGER.debug( pwmRequest, () -> "sending reCaptcha verification request" );
-            final PwmHttpClient client = pwmRequest.getPwmDomain().getHttpClientService().getPwmHttpClient( pwmRequest.getLabel() );
+            final PwmHttpClient client = pwmRequest.getClientConnectionHolder().getPwmHttpClient( null );
             final PwmHttpClientResponse clientResponse = client.makeRequest( clientRequest );
 
             if ( clientResponse.getStatusCode() != HttpServletResponse.SC_OK )

+ 6 - 6
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, pwmSession.getSessionManager().getActor( ) );
+            pwmPasswordRuleValidator.testPassword( pwmSession.getLabel(), newPassword, null, userInfo, pwmRequest.getClientConnectionHolder().getActor( ) );
         }
         catch ( final PwmDataValidationException e )
         {
@@ -302,7 +302,7 @@ public class PasswordUtility
         boolean setPasswordWithoutOld = false;
         if ( oldPassword == null )
         {
-            if ( pwmSession.getSessionManager().getActor( ).getChaiProvider().getDirectoryVendor() == DirectoryVendor.ACTIVE_DIRECTORY )
+            if ( pwmRequest.getClientConnectionHolder().getActor( ).getChaiProvider().getDirectoryVendor() == DirectoryVendor.ACTIVE_DIRECTORY )
             {
                 setPasswordWithoutOld = true;
             }
@@ -319,7 +319,7 @@ public class PasswordUtility
             }
         }
 
-        final ChaiProvider provider = pwmSession.getSessionManager().getChaiProvider();
+        final ChaiProvider provider = pwmRequest.getClientConnectionHolder().getActorChaiProvider();
 
         setPassword( pwmDomain, pwmRequest.getLabel(), provider, userInfo, setPasswordWithoutOld ? null : oldPassword, newPassword );
 
@@ -330,7 +330,7 @@ public class PasswordUtility
         pwmSession.getLoginInfoBean().setUserCurrentPassword( newPassword );
 
         //close any outstanding ldap connections (since they cache the old password)
-        pwmSession.getSessionManager().updateUserPassword( userInfo.getUserIdentity(), newPassword );
+        pwmRequest.getClientConnectionHolder().updateUserLdapPassword( userInfo.getUserIdentity(), newPassword );
 
         // clear the "requires new password flag"
         pwmSession.getLoginInfoBean().getLoginFlags().remove( LoginInfoBean.LoginFlag.forcePwChange );
@@ -342,7 +342,7 @@ public class PasswordUtility
         pwmSession.reloadUserInfoBean( pwmRequest );
 
         // create a proxy user object for pwm to update/read the user.
-        final ChaiUser proxiedUser = pwmSession.getSessionManager().getActor();
+        final ChaiUser proxiedUser = pwmRequest.getClientConnectionHolder().getActor();
 
         // update statistics
         {
@@ -733,7 +733,7 @@ public class PasswordUtility
 
                 final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmDomain, userIdentity )
                         .setExpandPwmMacros( true )
-                        .setMacroMachine( pwmRequest.getPwmSession().getSessionManager().getMacroMachine( ) )
+                        .setMacroMachine( pwmRequest.getMacroMachine() )
                         .createActionExecutor();
                 actionExecutor.executeActions( configValues, pwmRequest.getLabel() );
             }

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

@@ -200,7 +200,6 @@ ldap.cache.userGuid.seconds=3600
 ldap.chaiSettings=
 ldap.proxy.connectionsPerProfile=10
 ldap.proxy.maxConnections=50
-ldap.proxy.useThreadLocal=true
 ldap.proxy.idleThreadLocal.timeoutMS=90000
 ldap.extensions.nmas.enable=true
 ldap.connection.timeoutMS=30000

+ 3 - 0
webapp/src/main/webapp/META-INF/context.xml

@@ -22,4 +22,7 @@
 <Context tldValidation="false" unloadDelay="30000" useHttpOnly="true" mapperContextRootRedirectEnabled="true">
   <Manager pathname=""/>
   <!--pathname param prevents session persistence across restarts -->
+  <JarScanner>
+    <JarScanFilter tldSkip="*.*"/>
+  </JarScanner>
 </Context>

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

@@ -212,7 +212,7 @@ PWM_CFGEDIT.writeSetting = function(keyName, valueData, nextAction) {
     const errorFunction = function(error) {
         PWM_VAR['outstandingOperations']--;
         PWM_CFGEDIT.handleWorkingIcon();
-        PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Error'),text:"Unable to communicate with server.  Please refresh page."});
+        PWM_MAIN.showErrorDialog(error);
         console.log("error writing setting " + keyName + ", reason: " + error)
     };
     PWM_MAIN.ajaxRequest(url,loadFunction,{errorFunction:errorFunction,content:valueData});

+ 5 - 1
webapp/src/main/webapp/public/resources/js/main.js

@@ -1880,7 +1880,11 @@ PWM_MAIN.ajaxRequest = function(url,loadFunction,options) {
             if ( typeof response === "string" && handleAs === "json") {
                 response = JSON.parse( response );
             }
-            loadFunction(response);
+            if (xhr.status===200) {
+                loadFunction(response);
+            } else {
+                errorFunction(response)
+            }
         }
     };
     xhr.onerror = errorFunction;