瀏覽代碼

pwmhttpclient service refactoring

Jason Rivard 6 年之前
父節點
當前提交
49e437d0c2
共有 29 個文件被更改,包括 551 次插入303 次删除
  1. 2 2
      rest-test-service/src/main/java/password/pwm/resttest/RestTestPasswordCheckServlet.java
  2. 6 0
      server/src/main/java/password/pwm/PwmApplication.java
  3. 7 8
      server/src/main/java/password/pwm/health/ApplianceStatusChecker.java
  4. 0 1
      server/src/main/java/password/pwm/http/HttpContentType.java
  5. 1 1
      server/src/main/java/password/pwm/http/HttpEntityDataType.java
  6. 7 6
      server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java
  7. 7 6
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java
  8. 28 20
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java
  9. 6 0
      server/src/main/java/password/pwm/ldap/PhotoDataBean.java
  10. 1 0
      server/src/main/java/password/pwm/svc/PwmServiceEnum.java
  11. 3 1
      server/src/main/java/password/pwm/svc/email/EmailServerUtil.java
  12. 139 0
      server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java
  13. 13 59
      server/src/main/java/password/pwm/svc/httpclient/HttpTrustManagerHelper.java
  14. 91 45
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java
  15. 13 3
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientConfiguration.java
  16. 39 0
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientMessage.java
  17. 14 4
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientRequest.java
  18. 7 5
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientResponse.java
  19. 23 26
      server/src/main/java/password/pwm/svc/shorturl/TinyUrlShortener.java
  20. 6 7
      server/src/main/java/password/pwm/svc/telemetry/HttpTelemetrySender.java
  21. 4 5
      server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java
  22. 5 5
      server/src/main/java/password/pwm/util/CaptchaUtility.java
  23. 8 7
      server/src/main/java/password/pwm/util/operations/ActionExecutor.java
  24. 8 7
      server/src/main/java/password/pwm/util/queue/SmsQueueManager.java
  25. 21 29
      server/src/main/java/password/pwm/util/secure/X509Utils.java
  26. 8 9
      server/src/main/java/password/pwm/ws/client/rest/RestClientHelper.java
  27. 7 6
      server/src/main/java/password/pwm/ws/client/rest/form/RestFormDataClient.java
  28. 70 40
      server/src/test/java/password/pwm/http/client/PwmHttpClientTest.java
  29. 7 1
      server/src/test/java/password/pwm/util/localdb/TestHelper.java

+ 2 - 2
rest-test-service/src/main/java/password/pwm/resttest/RestTestPasswordCheckServlet.java

@@ -49,8 +49,8 @@ public class RestTestPasswordCheckServlet extends HttpServlet
                 }.getType() );
         final String inputPassword = inputJson.get( "password" );
         final boolean error = inputPassword.contains( "aaa" );
-
-        final String errorMessage = error ? "TOO Many aaa's (REMOTE REST SERVICE)" : "No error. (REMOTE REST SERVICE)";
+        final String errorMessage = ( error ? "TOO Many aaa's (REMOTE REST SERVICE)" : "No error. (REMOTE REST SERVICE)" )
+                + ", pw=" + inputPassword;
 
         resp.setHeader( "Content-Type", "application/json" );
         final PrintWriter writer = resp.getWriter();

+ 6 - 0
server/src/main/java/password/pwm/PwmApplication.java

@@ -46,6 +46,7 @@ import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditService;
 import password.pwm.svc.event.SystemAuditRecord;
+import password.pwm.svc.httpclient.HttpClientService;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.node.NodeService;
@@ -551,6 +552,11 @@ public class PwmApplication
         return ( HealthMonitor ) pwmServiceManager.getService( HealthMonitor.class );
     }
 
+    public HttpClientService getHttpClientService()
+    {
+        return ( HttpClientService ) pwmServiceManager.getService( HttpClientService.class );
+    }
+
     public List<PwmService> getPwmServices( )
     {
         final List<PwmService> pwmServices = new ArrayList<>();

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

@@ -29,14 +29,13 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.X509Utils;
 
 import java.io.File;
 import java.io.IOException;
@@ -89,17 +88,17 @@ public class ApplianceStatusChecker implements HealthChecker
         final Map<String, String> requestHeaders = Collections.singletonMap( "sspr-authorization-token", getApplianceAccessToken( pwmApplication ) );
 
         final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
-                .trustManager( new X509Utils.PromiscuousTrustManager( SessionLabel.HEALTH_SESSION_LABEL ) )
+                .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous )
                 .build();
 
-        final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmApplication, SessionLabel.HEALTH_SESSION_LABEL, pwmHttpClientConfiguration );
+        final PwmHttpClient pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( pwmHttpClientConfiguration );
         final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder()
                 .method( HttpMethod.GET )
                 .url( url )
                 .headers( requestHeaders )
                 .build();
 
-        final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+        final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest, SessionLabel.HEALTH_SESSION_LABEL );
 
         LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "https response from appliance server request: " + response.getBody() );
 

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

@@ -21,7 +21,6 @@
 package password.pwm.http;
 
 import password.pwm.PwmConstants;
-import password.pwm.http.client.HttpEntityDataType;
 import password.pwm.util.java.StringUtil;
 
 import java.nio.charset.Charset;

+ 1 - 1
server/src/main/java/password/pwm/http/client/HttpEntityDataType.java → server/src/main/java/password/pwm/http/HttpEntityDataType.java

@@ -18,7 +18,7 @@
  * limitations under the License.
  */
 
-package password.pwm.http.client;
+package password.pwm.http;
 
 public enum HttpEntityDataType
 {

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

@@ -35,9 +35,9 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.ldap.UserInfo;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -114,9 +114,10 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
     }
 
     @Override
-    public void init( final PwmApplication pwmApplication, final UserInfo userInfo, final SessionLabel sessionLabel, final Locale locale ) throws PwmUnrecoverableException
+    public void init( final PwmApplication pwmApplication, final UserInfo userInfo, final SessionLabel sessionLabel, final Locale locale )
+            throws PwmUnrecoverableException
     {
-        pwmHttpClient = new PwmHttpClient( pwmApplication, sessionLabel );
+        pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( );
         this.remoteSessionID = pwmApplication.getSecureService().pwmRandom().randomUUID().toString();
         this.userInfo = userInfo;
         this.sessionLabel = sessionLabel;
@@ -159,7 +160,7 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
 
         try
         {
-            final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+            final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest, this.sessionLabel );
             final String responseBodyStr = response.getBody();
             this.lastResponse = JsonUtil.deserialize( responseBodyStr, RemoteVerificationResponseBean.class );
         }

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

@@ -36,10 +36,10 @@ import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.java.JavaHelper;
@@ -295,11 +295,12 @@ public class OAuthMachine
         try
         {
             final PwmHttpClientConfiguration config = PwmHttpClientConfiguration.builder()
+                    .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
                     .certificates( JavaHelper.isEmpty( certs ) ? null : certs )
                     .maskBodyDebugOutput( true )
                     .build();
-            final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), config );
-            pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+            final PwmHttpClient pwmHttpClient = pwmRequest.getPwmApplication().getHttpClientService().getPwmHttpClient( config );
+            pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest, pwmRequest.getSessionLabel() );
         }
         catch ( PwmException e )
         {

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

@@ -38,19 +38,18 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.ImmutableByteArray;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.PhotoDataBean;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
-import password.pwm.util.secure.X509Utils;
 
 import javax.servlet.http.HttpServletResponse;
 import java.io.OutputStream;
@@ -176,31 +175,40 @@ public class PhotoDataReader
 
         final PhotoReaderMethod method = figurePhotoDataReaderMethod( );
 
+        Optional<PhotoDataBean> photoDataBean = Optional.empty();
         try
         {
             switch ( method )
             {
-                case ClientHttp:
-                    return  Optional.empty();
-
                 case Ldap:
-                    return readPhotoDataFromLdap();
+                    photoDataBean = readPhotoDataFromLdap();
+                    break;
 
                 case ServerHttp:
-                    return readPhotoDataFromHTTP();
+                    photoDataBean = readPhotoDataFromHTTP();
+                    break;
 
                 default:
                     JavaHelper.unhandledSwitchStatement( method );
-
             }
-
-            return  Optional.empty();
         }
         finally
         {
-            LOGGER.trace( pwmRequest, () -> "read user photo data of " + userIdentity.toDisplayString() + " in "
-                    + TimeDuration.compactFromCurrent( startTime ) );
+            final Optional<PhotoDataBean> finalData = photoDataBean;
+            if ( finalData.isPresent() )
+            {
+                LOGGER.trace( pwmRequest, () -> "user photo data received for " + userIdentity.toDisplayString()
+                        + " " + finalData.get().toString()
+                        + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
+            }
+            else
+            {
+                LOGGER.trace( pwmRequest, () -> "no user photo data received for " + userIdentity.toDisplayString()
+                        + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
+            }
         }
+
+        return photoDataBean;
     }
 
     private Optional<PhotoDataBean> readPhotoDataFromLdap()
@@ -225,17 +233,17 @@ public class PhotoDataReader
         try
         {
             final PwmHttpClientConfiguration configuration = PwmHttpClientConfiguration.builder()
-                    .trustManager( new X509Utils.PromiscuousTrustManager( pwmRequest.getSessionLabel() ) )
+                    .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous )
                     .build();
-            final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), configuration );
+            final PwmHttpClient pwmHttpClient = pwmRequest.getPwmApplication().getHttpClientService().getPwmHttpClient( configuration );
             final PwmHttpClientRequest clientRequest = PwmHttpClientRequest.builder()
                     .method( HttpMethod.GET )
                     .url( overrideURL.get() )
                     .build();
-            final PwmHttpClientResponse response = pwmHttpClient.makeRequest( clientRequest );
+            final PwmHttpClientResponse response = pwmHttpClient.makeRequest( clientRequest, pwmRequest.getSessionLabel() );
             if ( response != null )
             {
-                final ImmutableByteArray bodyContents = response.getByteBody();
+                final ImmutableByteArray bodyContents = response.getBinaryBody();
                 if ( bodyContents != null && !bodyContents.isEmpty() )
                 {
                     final String mimeType = response.getContentType().getMimeType();
@@ -246,7 +254,7 @@ public class PhotoDataReader
         }
         catch ( Exception e )
         {
-            final String msg = "error reading remote http photo data: " + e.getMessage();
+            final String msg = "error reading remote http photo data: " + JavaHelper.readHostileExceptionMessage( e );
             throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_SERVICE_UNREACHABLE, msg ) );
         }
     }

+ 6 - 0
server/src/main/java/password/pwm/ldap/PhotoDataBean.java

@@ -30,4 +30,10 @@ public class PhotoDataBean implements Serializable
 {
     private String mimeType;
     private ImmutableByteArray contents;
+
+    @Override
+    public String toString()
+    {
+        return "[image " + contents.size() + " bytes, mime=" + mimeType +  "]";
+    }
 }

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

@@ -35,6 +35,7 @@ public enum PwmServiceEnum
 {
     LocalDBService( password.pwm.util.localdb.LocalDBService.class, Flag.StartDuringRuntimeInstance ),
     SecureService( password.pwm.util.secure.SecureService.class, Flag.StartDuringRuntimeInstance ),
+    HttpClientService( password.pwm.svc.httpclient.HttpClientService.class, Flag.StartDuringRuntimeInstance ),
     LdapConnectionService( password.pwm.ldap.LdapConnectionService.class, Flag.StartDuringRuntimeInstance ),
     DatabaseService( password.pwm.util.db.DatabaseService.class, Flag.StartDuringRuntimeInstance ),
     SharedHistoryManager( password.pwm.svc.wordlist.SharedHistoryManager.class ),

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

@@ -386,7 +386,9 @@ public class EmailServerUtil
             throws PwmUnrecoverableException
     {
         final EmailServerProfile emailServerProfile = configuration.getEmailServerProfiles().get( profile );
-        final X509Utils.CertReaderTrustManager certReaderTm = new X509Utils.CertReaderTrustManager( X509Utils.ReadCertificateFlag.ReadOnlyRootCA );
+        final X509Utils.CertReaderTrustManager certReaderTm = new X509Utils.CertReaderTrustManager(
+                new X509Utils.PromiscuousTrustManager(),
+                X509Utils.ReadCertificateFlag.ReadOnlyRootCA );
         final TrustManager[] trustManagers =  new TrustManager[]
                 {
                         certReaderTm,

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

@@ -0,0 +1,139 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 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.svc.httpclient;
+
+import password.pwm.PwmApplication;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.health.HealthRecord;
+import password.pwm.svc.PwmService;
+import password.pwm.util.logging.PwmLogger;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class HttpClientService implements PwmService
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( HttpClientService.class );
+
+    private PwmApplication pwmApplication;
+
+    private final Map<PwmHttpClientConfiguration, ThreadLocal<PwmHttpClient>> clients = new ConcurrentHashMap<>(  );
+    private final Map<PwmHttpClient, Object> issuedClients = Collections.synchronizedMap( new WeakHashMap<>(  ) );
+
+    private final Map<StatsKey, AtomicInteger> stats = new HashMap<>( );
+
+    enum StatsKey
+    {
+        createdClients,
+        reusedClients,
+    }
+
+    public HttpClientService()
+            throws PwmUnrecoverableException
+    {
+        for ( final StatsKey statsKey : StatsKey.values() )
+        {
+            stats.put( statsKey, new AtomicInteger( 0 ) );
+        }
+    }
+
+    @Override
+    public STATUS status()
+    {
+        return STATUS.OPEN;
+    }
+
+    @Override
+    public void init( final PwmApplication pwmApplication )
+            throws PwmException
+    {
+        this.pwmApplication = pwmApplication;
+    }
+
+    @Override
+    public void close()
+    {
+        for ( final PwmHttpClient pwmHttpClient : new HashSet<>( issuedClients.keySet() ) )
+        {
+            try
+            {
+                pwmHttpClient.close();
+            }
+            catch ( Exception e )
+            {
+                LOGGER.debug( () -> "error closing pwmHttpClient instance: " + e.getMessage() );
+            }
+        }
+    }
+
+    public PwmHttpClient getPwmHttpClient()
+            throws PwmUnrecoverableException
+    {
+        return this.getPwmHttpClient( PwmHttpClientConfiguration.builder().build() );
+    }
+
+    public PwmHttpClient getPwmHttpClient( final PwmHttpClientConfiguration pwmHttpClientConfiguration )
+            throws PwmUnrecoverableException
+    {
+        Objects.requireNonNull( pwmHttpClientConfiguration );
+
+        final ThreadLocal<PwmHttpClient> threadLocal = clients.computeIfAbsent(
+                pwmHttpClientConfiguration,
+                clientConfig -> new ThreadLocal<>() );
+
+        final PwmHttpClient existingClient = threadLocal.get();
+        if ( existingClient != null && !existingClient.isClosed() )
+        {
+            stats.get( StatsKey.reusedClients ).incrementAndGet();
+            return existingClient;
+        }
+
+        final PwmHttpClient newClient = new PwmHttpClient( pwmApplication, pwmHttpClientConfiguration );
+        issuedClients.put( newClient, null );
+        threadLocal.set( newClient );
+        stats.get( StatsKey.createdClients ).incrementAndGet();
+        return newClient;
+    }
+
+    @Override
+    public List<HealthRecord> healthCheck()
+    {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public ServiceInfoBean serviceInfo()
+    {
+        final Map<String, String> debugMap = new HashMap<>(  );
+        stats.forEach( ( key, value ) -> debugMap.put( key.name(), value.toString() ) );
+        debugMap.put( "weakReferences", Integer.toString( issuedClients.size() ) );
+        debugMap.put( "referencedConfigs", Integer.toString( clients.size() ) );
+        return new ServiceInfoBean( Collections.emptyList(), debugMap );
+    }
+}

+ 13 - 59
server/src/main/java/password/pwm/http/client/HttpTrustManagerHelper.java → server/src/main/java/password/pwm/svc/httpclient/HttpTrustManagerHelper.java

@@ -18,12 +18,11 @@
  * limitations under the License.
  */
 
-package password.pwm.http.client;
+package password.pwm.svc.httpclient;
 
 import org.apache.http.conn.ssl.DefaultHostnameVerifier;
 import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import password.pwm.AppProperty;
-import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -39,68 +38,29 @@ import java.util.Iterator;
 class HttpTrustManagerHelper
 {
     private final Configuration configuration;
-    private final SessionLabel sessionLabel;
     private final PwmHttpClientConfiguration pwmHttpClientConfiguration;
-    private final TrustManagerType trustManagerType;
-
-    enum TrustManagerType
-    {
-        promiscuous,
-        supplied,
-        configuredCertificates,
-        defaultJava,
-    }
+    private final PwmHttpClientConfiguration.TrustManagerType trustManagerType;
 
     HttpTrustManagerHelper(
             final Configuration configuration,
-            final SessionLabel sessionLabel,
             final PwmHttpClientConfiguration pwmHttpClientConfiguration
     )
     {
         this.configuration = configuration;
-        this.sessionLabel = sessionLabel;
         this.pwmHttpClientConfiguration = pwmHttpClientConfiguration;
-        this.trustManagerType = figureType();
+        this.trustManagerType = pwmHttpClientConfiguration.getTrustManagerType();
     }
 
-    TrustManagerType getTrustManagerType()
+    PwmHttpClientConfiguration.TrustManagerType getTrustManagerType()
     {
         return trustManagerType;
     }
 
-    private TrustManagerType figureType()
-    {
-
-        final boolean configPromiscuousEnabled = Boolean.parseBoolean( configuration.readAppProperty( AppProperty.SECURITY_HTTP_PROMISCUOUS_ENABLE ) );
-        final boolean promiscuousTrustMgrSet = pwmHttpClientConfiguration != null
-                && pwmHttpClientConfiguration.getTrustManager() != null
-                && X509Utils.PromiscuousTrustManager.class.equals( pwmHttpClientConfiguration.getTrustManager().getClass() );
-
-        if ( configPromiscuousEnabled || promiscuousTrustMgrSet )
-        {
-            return TrustManagerType.promiscuous;
-        }
-
-        // use the client supplied TrustManager
-        if ( pwmHttpClientConfiguration.getTrustManager() != null )
-        {
-            return TrustManagerType.supplied;
-        }
-
-        // using configured certificates
-        if ( !JavaHelper.isEmpty( pwmHttpClientConfiguration.getCertificates() ) )
-        {
-            return TrustManagerType.configuredCertificates;
-        }
-
-        // use default trust manager
-        return TrustManagerType.defaultJava;
-    }
 
     HostnameVerifier hostnameVerifier()
     {
-        final TrustManagerType trustManagerType = getTrustManagerType();
-        if ( trustManagerType == TrustManagerType.promiscuous )
+        final PwmHttpClientConfiguration.TrustManagerType trustManagerType = getTrustManagerType();
+        if ( trustManagerType == PwmHttpClientConfiguration.TrustManagerType.promiscuous )
         {
             return NoopHostnameVerifier.INSTANCE;
         }
@@ -117,23 +77,21 @@ class HttpTrustManagerHelper
     )
             throws PwmUnrecoverableException
     {
-        final TrustManagerType trustManagerType = getTrustManagerType();
+        final PwmHttpClientConfiguration.TrustManagerType trustManagerType = getTrustManagerType();
 
         switch ( trustManagerType )
         {
             case promiscuous:
                 return new TrustManager[]
                         {
-                                new X509Utils.PromiscuousTrustManager( sessionLabel ),
+                                new X509Utils.PromiscuousTrustManager( ),
                         };
 
-            case supplied:
-            {
+            case promiscuousCertReader:
                 return new TrustManager[]
                         {
-                                pwmHttpClientConfiguration.getTrustManager(),
+                                new X509Utils.CertReaderTrustManager( new X509Utils.PromiscuousTrustManager( ) ),
                         };
-            }
 
             case configuredCertificates:
             {
@@ -158,14 +116,10 @@ class HttpTrustManagerHelper
 
     String debugText() throws PwmUnrecoverableException
     {
-        final TrustManagerType type = getTrustManagerType();
+        final PwmHttpClientConfiguration.TrustManagerType type = getTrustManagerType();
         final StringBuilder value = new StringBuilder( "trust manager [" + type );
-        if ( TrustManagerType.supplied == type )
-        {
-            value.append( "=" );
-            value.append( pwmHttpClientConfiguration.getTrustManager().getClass().getSimpleName() );
-        }
-        else if ( TrustManagerType.configuredCertificates == type )
+
+        if ( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates == type )
         {
             value.append( "=" );
             for ( final Iterator<X509Certificate> iterator = pwmHttpClientConfiguration.getCertificates().iterator(); iterator.hasNext(); )

+ 91 - 45
server/src/main/java/password/pwm/http/client/PwmHttpClient.java → server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java

@@ -18,7 +18,7 @@
  * limitations under the License.
  */
 
-package password.pwm.http.client;
+package password.pwm.svc.httpclient;
 
 import org.apache.commons.io.input.CountingInputStream;
 import org.apache.http.Header;
@@ -31,7 +31,6 @@ import org.apache.http.HttpStatus;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.CredentialsProvider;
-import org.apache.http.client.HttpClient;
 import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
@@ -49,6 +48,7 @@ import org.apache.http.conn.socket.PlainConnectionSocketFactory;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.client.ProxyAuthenticationStrategy;
 import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
@@ -64,6 +64,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
+import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmURL;
@@ -73,8 +74,10 @@ 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.secure.X509Utils;
 
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -82,7 +85,9 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -90,53 +95,73 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
-public class PwmHttpClient
+public class PwmHttpClient implements AutoCloseable
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmHttpClient.class );
 
-    private static final AtomicInteger REQUEST_COUNTER = new AtomicInteger( 0 );
+    private static final AtomicInteger CLIENT_COUNTER = new AtomicInteger( 0 );
 
+    private final int clientID = CLIENT_COUNTER.getAndIncrement();
     private final PwmApplication pwmApplication;
-    private final SessionLabel sessionLabel;
     private final PwmHttpClientConfiguration pwmHttpClientConfiguration;
 
-    public PwmHttpClient( final PwmApplication pwmApplication, final SessionLabel sessionLabel )
+    private final TrustManager[] trustManagers;
+    private final CloseableHttpClient httpClient;
+
+    private volatile boolean open = true;
+
+    PwmHttpClient( final PwmApplication pwmApplication, final PwmHttpClientConfiguration pwmHttpClientConfiguration )
+            throws PwmUnrecoverableException
     {
         this.pwmApplication = pwmApplication;
-        this.sessionLabel = sessionLabel;
-        this.pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder().certificates( null ).build();
+        this.pwmHttpClientConfiguration = pwmHttpClientConfiguration;
+
+        this.trustManagers = makeTrustManager( pwmApplication.getConfig(), pwmHttpClientConfiguration );
+        this.httpClient = makeHttpClient( pwmApplication, pwmHttpClientConfiguration, this.trustManagers );
     }
 
-    public PwmHttpClient( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final PwmHttpClientConfiguration pwmHttpClientConfiguration )
+    @Override
+    public void close()
+            throws Exception
     {
-        this.pwmApplication = pwmApplication;
-        this.sessionLabel = sessionLabel;
-        this.pwmHttpClientConfiguration = pwmHttpClientConfiguration;
+        LOGGER.trace( () -> "closed client #" + clientID );
+        httpClient.close();
+        open = false;
     }
 
-    public static HttpClient getHttpClient( final Configuration configuration )
-            throws PwmUnrecoverableException
+    boolean isClosed()
     {
-        return getHttpClient( configuration, PwmHttpClientConfiguration.builder().certificates( null ).build(), null );
+        return !open;
     }
 
-    static HttpClient getHttpClient(
+    private static TrustManager[] makeTrustManager(
             final Configuration configuration,
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration
+    )
+            throws PwmUnrecoverableException
+    {
+        final HttpTrustManagerHelper httpTrustManagerHelper = new HttpTrustManagerHelper( configuration, pwmHttpClientConfiguration );
+        return httpTrustManagerHelper.makeTrustManager();
+    }
+
+    private static CloseableHttpClient makeHttpClient(
+            final PwmApplication pwmApplication,
             final PwmHttpClientConfiguration pwmHttpClientConfiguration,
-            final SessionLabel sessionLabel
+            final TrustManager[] trustManagers
     )
             throws PwmUnrecoverableException
     {
+        final Configuration configuration = pwmApplication.getConfig();
         final HttpClientBuilder clientBuilder = HttpClientBuilder.create();
-        clientBuilder.setUserAgent( PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION );
+        clientBuilder.setUserAgent( PwmConstants.PWM_APP_NAME );
+        final HttpTrustManagerHelper httpTrustManagerHelper = new HttpTrustManagerHelper( configuration, pwmHttpClientConfiguration );
 
         try
         {
             final SSLContext sslContext = SSLContext.getInstance( "TLS" );
-            final HttpTrustManagerHelper httpTrustManagerHelper = new HttpTrustManagerHelper( configuration, sessionLabel, pwmHttpClientConfiguration );
             sslContext.init(
                     null,
-                    httpTrustManagerHelper.makeTrustManager(),
+                    trustManagers,
                     new SecureRandom() );
             final SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory( sslContext, httpTrustManagerHelper.hostnameVerifier() );
             final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
@@ -177,7 +202,7 @@ public class PwmHttpClient
                 clientBuilder.setProxyAuthenticationStrategy( new ProxyAuthenticationStrategy() );
             }
 
-            clientBuilder.setRoutePlanner( new ProxyRoutePlanner( proxyHost, configuration, sessionLabel ) );
+            clientBuilder.setRoutePlanner( new ProxyRoutePlanner( proxyHost, configuration ) );
         }
 
         clientBuilder.setDefaultRequestConfig( RequestConfig.copy( RequestConfig.DEFAULT )
@@ -191,12 +216,14 @@ public class PwmHttpClient
 
     String entityToDebugString(
             final String topLine,
-            final Map<String, String> headers,
-            final HttpEntityDataType dataType,
-            final String body,
-            final ImmutableByteArray binaryBody
+            final PwmHttpClientMessage pwmHttpClientMessage
     )
     {
+        final HttpEntityDataType dataType = pwmHttpClientMessage.getDataType();
+        final ImmutableByteArray binaryBody = pwmHttpClientMessage.getBinaryBody();
+        final String body = pwmHttpClientMessage.getBody();
+        final Map<String, String> headers = pwmHttpClientMessage.getHeaders();
+
         final boolean isBinary = dataType == HttpEntityDataType.ByteArray;
         final boolean emptyBody = isBinary
                 ? binaryBody == null || binaryBody.isEmpty()
@@ -205,17 +232,18 @@ public class PwmHttpClient
 
         final StringBuilder msg = new StringBuilder();
         msg.append( topLine );
+        msg.append( " id=" ).append( pwmHttpClientMessage.getRequestID() ).append( ") " );
 
         if ( emptyBody )
         {
             msg.append( " (no body)" );
         }
-        msg.append( "\n" );
 
         if ( headers != null )
         {
             for ( final Map.Entry<String, String> headerEntry : headers.entrySet() )
             {
+                msg.append( "\n" );
                 final HttpHeader httpHeader = HttpHeader.forHttpHeader( headerEntry.getKey() );
                 if ( httpHeader != null )
                 {
@@ -237,13 +265,12 @@ public class PwmHttpClient
                     // so we can't check the sensitive flag.
                     msg.append( "  header: " ).append( headerEntry.getKey() ).append( "=" ).append( headerEntry.getValue() );
                 }
-                msg.append( "\n" );
             }
         }
 
         if ( !emptyBody )
         {
-            msg.append( "  body: " );
+            msg.append( "\n  body: " );
 
             final boolean alwaysOutput = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_CLIENT_ALWAYS_LOG_ENTITIES ) );
 
@@ -284,11 +311,15 @@ public class PwmHttpClient
         return msg.toString();
     }
 
-    public PwmHttpClientResponse makeRequest( final PwmHttpClientRequest request ) throws PwmUnrecoverableException
+    public PwmHttpClientResponse makeRequest(
+            final PwmHttpClientRequest clientRequest,
+            final SessionLabel sessionLabel
+    )
+            throws PwmUnrecoverableException
     {
         try
         {
-            return makeRequestImpl( request );
+            return makeRequestImpl( clientRequest, sessionLabel );
         }
         catch ( IOException e )
         {
@@ -296,18 +327,19 @@ public class PwmHttpClient
         }
     }
 
-    private PwmHttpClientResponse makeRequestImpl( final PwmHttpClientRequest clientRequest )
+    private PwmHttpClientResponse makeRequestImpl(
+            final PwmHttpClientRequest clientRequest,
+            final SessionLabel sessionLabel
+    )
             throws IOException, PwmUnrecoverableException
     {
         final Instant startTime = Instant.now();
-        final int counter = REQUEST_COUNTER.getAndIncrement();
-
         if ( LOGGER.isEnabled( PwmLogLevel.TRACE ) )
         {
             final String sslDebugText;
             if ( clientRequest.isHttps() )
             {
-                final HttpTrustManagerHelper httpTrustManagerHelper = new HttpTrustManagerHelper( pwmApplication.getConfig(), sessionLabel, pwmHttpClientConfiguration );
+                final HttpTrustManagerHelper httpTrustManagerHelper = new HttpTrustManagerHelper( pwmApplication.getConfig(), pwmHttpClientConfiguration );
                 sslDebugText = "using " + httpTrustManagerHelper.debugText();
             }
             else
@@ -315,24 +347,26 @@ public class PwmHttpClient
                 sslDebugText = "";
             }
 
-            LOGGER.trace( sessionLabel, () -> "preparing to send (id=" + counter + ") "
+            LOGGER.trace( sessionLabel, () -> "client #" + clientID + " preparing to send "
                     + clientRequest.toDebugString( this, sslDebugText ) );
         }
 
         final HttpResponse httpResponse = executeRequest( clientRequest );
 
         final PwmHttpClientResponse.PwmHttpClientResponseBuilder httpClientResponseBuilder = PwmHttpClientResponse.builder();
+        httpClientResponseBuilder.requestID( clientRequest.getRequestID() );
+
         final HttpContentType httpContentType = contentTypeForEntity( httpResponse.getEntity() );
 
         if ( httpContentType != null && httpContentType.getDataType() ==  HttpEntityDataType.ByteArray )
         {
-            httpClientResponseBuilder.byteBody( readBinaryEntityBody( httpResponse.getEntity() ) );
-            httpClientResponseBuilder.httpEntityDataType( HttpEntityDataType.ByteArray );
+            httpClientResponseBuilder.binaryBody( readBinaryEntityBody( httpResponse.getEntity() ) );
+            httpClientResponseBuilder.dataType( HttpEntityDataType.ByteArray );
         }
         else
         {
             httpClientResponseBuilder.body( EntityUtils.toString( httpResponse.getEntity() ) );
-            httpClientResponseBuilder.httpEntityDataType( HttpEntityDataType.String );
+            httpClientResponseBuilder.dataType( HttpEntityDataType.String );
         }
 
         final Map<String, String> responseHeaders = new LinkedHashMap<>();
@@ -349,7 +383,7 @@ public class PwmHttpClient
                 .build();
 
         final TimeDuration duration = TimeDuration.fromCurrent( startTime );
-        LOGGER.trace( sessionLabel, () -> "received response (id=" + counter + ") in "
+        LOGGER.trace( sessionLabel, () -> "client #" + clientID + " received response (id=" + clientRequest.getRequestID() + ") in "
                 + duration.asCompactString() + ": "
                 + httpClientResponse.toDebugString( this ) );
         return httpClientResponse;
@@ -417,7 +451,6 @@ public class PwmHttpClient
             }
         }
 
-        final HttpClient httpClient = getHttpClient( pwmApplication.getConfig(), pwmHttpClientConfiguration, sessionLabel );
         return httpClient.execute( httpRequest );
     }
 
@@ -456,14 +489,11 @@ public class PwmHttpClient
     {
         private final HttpHost proxyServer;
         private final Configuration configuration;
-        private final SessionLabel sessionLabel;
 
-
-        ProxyRoutePlanner( final HttpHost proxyServer, final Configuration configuration, final SessionLabel sessionLabel )
+        ProxyRoutePlanner( final HttpHost proxyServer, final Configuration configuration )
         {
             this.proxyServer = proxyServer;
             this.configuration = configuration;
-            this.sessionLabel = sessionLabel;
         }
 
         public HttpRoute determineRoute(
@@ -476,7 +506,7 @@ public class PwmHttpClient
 
             final List<String> proxyExceptionUrls = configuration.readSettingAsStringArray( PwmSetting.HTTP_PROXY_EXCEPTIONS );
 
-            if ( PwmURL.testIfUrlMatchesAllowedPattern( targetUri, proxyExceptionUrls, sessionLabel ) )
+            if ( PwmURL.testIfUrlMatchesAllowedPattern( targetUri, proxyExceptionUrls, null ) )
             {
                 return new HttpRoute( target );
             }
@@ -529,5 +559,21 @@ public class PwmHttpClient
             return ImmutableByteArray.of( baos.toByteArray() );
         }
     }
+
+    public List<X509Certificate> readServerCertificates()
+    {
+        final List<X509Certificate> returnList = new ArrayList<>(  );
+        if ( trustManagers != null )
+        {
+            for ( final TrustManager trustManager : trustManagers )
+            {
+                if ( trustManager instanceof X509Utils.CertReaderTrustManager )
+                {
+                    returnList.addAll( ( (X509Utils.CertReaderTrustManager) trustManager ).getCertificates() );
+                }
+            }
+        }
+        return Collections.unmodifiableList( returnList );
+    }
 }
 

+ 13 - 3
server/src/main/java/password/pwm/http/client/PwmHttpClientConfiguration.java → server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientConfiguration.java

@@ -18,12 +18,11 @@
  * limitations under the License.
  */
 
-package password.pwm.http.client;
+package password.pwm.svc.httpclient;
 
 import lombok.Builder;
 import lombok.Value;
 
-import javax.net.ssl.X509TrustManager;
 import java.security.cert.X509Certificate;
 import java.util.List;
 
@@ -32,6 +31,17 @@ import java.util.List;
 public class PwmHttpClientConfiguration
 {
     private List<X509Certificate> certificates;
-    private X509TrustManager trustManager;
+
+    @Builder.Default
+    private TrustManagerType trustManagerType = TrustManagerType.defaultJava;
+
     private boolean maskBodyDebugOutput;
+
+    public enum TrustManagerType
+    {
+        promiscuousCertReader,
+        promiscuous,
+        configuredCertificates,
+        defaultJava,
+    }
 }

+ 39 - 0
server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientMessage.java

@@ -0,0 +1,39 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 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.svc.httpclient;
+
+import password.pwm.http.HttpEntityDataType;
+import password.pwm.http.bean.ImmutableByteArray;
+
+import java.util.Map;
+
+public interface PwmHttpClientMessage
+{
+    int getRequestID();
+
+    String getBody();
+
+    Map<String, String> getHeaders();
+
+    HttpEntityDataType getDataType();
+
+    ImmutableByteArray getBinaryBody();
+}

+ 14 - 4
server/src/main/java/password/pwm/http/client/PwmHttpClientRequest.java → server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientRequest.java

@@ -18,10 +18,11 @@
  * limitations under the License.
  */
 
-package password.pwm.http.client;
+package password.pwm.svc.httpclient;
 
 import lombok.Builder;
 import lombok.Value;
+import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.util.java.StringUtil;
@@ -30,11 +31,16 @@ import java.io.Serializable;
 import java.net.URI;
 import java.util.Collections;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @Value
-@Builder
-public class PwmHttpClientRequest implements Serializable
+@Builder( toBuilder = true )
+public class PwmHttpClientRequest implements Serializable, PwmHttpClientMessage
 {
+    private static final AtomicInteger REQUEST_COUNTER = new AtomicInteger( 0 );
+
+    private final int requestID = REQUEST_COUNTER.incrementAndGet();
+
     @Builder.Default
     private final HttpMethod method = HttpMethod.GET;
 
@@ -42,6 +48,10 @@ public class PwmHttpClientRequest implements Serializable
 
     private final String body;
 
+    private final HttpEntityDataType dataType = HttpEntityDataType.String;
+
+    private final ImmutableByteArray binaryBody = null;
+
     @Builder.Default
     private final Map<String, String> headers = Collections.emptyMap();
 
@@ -51,7 +61,7 @@ public class PwmHttpClientRequest implements Serializable
                 + ( StringUtil.isEmpty( additionalText )
                 ? ""
                 : " " + additionalText );
-        return pwmHttpClient.entityToDebugString( topLine, headers, HttpEntityDataType.String, body, ImmutableByteArray.empty() );
+        return pwmHttpClient.entityToDebugString( topLine, this );
     }
 
     public boolean isHttps()

+ 7 - 5
server/src/main/java/password/pwm/http/client/PwmHttpClientResponse.java → server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientResponse.java

@@ -18,11 +18,12 @@
  * limitations under the License.
  */
 
-package password.pwm.http.client;
+package password.pwm.svc.httpclient;
 
 import lombok.Builder;
 import lombok.Value;
 import password.pwm.http.HttpContentType;
+import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.bean.ImmutableByteArray;
 
 import java.io.Serializable;
@@ -30,23 +31,24 @@ import java.util.Map;
 
 @Value
 @Builder
-public class PwmHttpClientResponse implements Serializable
+public class PwmHttpClientResponse implements Serializable, PwmHttpClientMessage
 {
+    private final int requestID;
     private final int statusCode;
     private final String statusPhrase;
     private final Map<String, String> headers;
 
     @Builder.Default
-    private final HttpEntityDataType httpEntityDataType = HttpEntityDataType.String;
+    private final HttpEntityDataType dataType = HttpEntityDataType.String;
 
     private final HttpContentType contentType;
 
     private final String body;
-    private final ImmutableByteArray byteBody;
+    private final ImmutableByteArray binaryBody;
 
     public String toDebugString( final PwmHttpClient pwmHttpClient )
     {
         final String topLine = "HTTP response status " + statusCode + " " + statusPhrase;
-        return pwmHttpClient.entityToDebugString( topLine, headers, httpEntityDataType, body, byteBody );
+        return pwmHttpClient.entityToDebugString( topLine, this );
     }
 }

+ 23 - 26
server/src/main/java/password/pwm/svc/shorturl/TinyUrlShortener.java

@@ -20,13 +20,12 @@
 
 package password.pwm.svc.shorturl;
 
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.util.EntityUtils;
 import password.pwm.PwmApplication;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.client.PwmHttpClient;
+import password.pwm.http.HttpMethod;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
@@ -49,31 +48,29 @@ public class TinyUrlShortener extends BasicUrlShortener
         this.configuration = configuration;
     }
 
-    public String shorten( final String input, final PwmApplication context ) throws PwmUnrecoverableException
+    public String shorten( final String input, final PwmApplication context )
+            throws PwmUnrecoverableException
     {
-        try
+        LOGGER.debug( () -> "Trying to shorten url: " + input );
+        final String encodedUrl = StringUtil.urlEncode( input );
+        final String callUrl = apiUrl + encodedUrl;
+        final PwmHttpClient pwmHttpClient = context.getHttpClientService().getPwmHttpClient(  );
+
+        final PwmHttpClientRequest request = PwmHttpClientRequest.builder()
+                .method( HttpMethod.GET )
+                .url( callUrl )
+                .build();
+        final PwmHttpClientResponse httpResponse = pwmHttpClient.makeRequest( request, null );
+        final int httpResponseCode = httpResponse.getStatusCode();
+        if ( httpResponseCode == 200 )
         {
-            LOGGER.debug( () -> "Trying to shorten url: " + input );
-            final String encodedUrl = StringUtil.urlEncode( input );
-            final String callUrl = apiUrl + encodedUrl;
-            final HttpClient httpClient = PwmHttpClient.getHttpClient( context.getConfig() );
-            final HttpGet httpRequest = new HttpGet( callUrl );
-            final HttpResponse httpResponse = httpClient.execute( httpRequest );
-            final int httpResponseCode = httpResponse.getStatusLine().getStatusCode();
-            if ( httpResponseCode == 200 )
-            {
-                final String responseBody = EntityUtils.toString( httpResponse.getEntity() );
-                LOGGER.debug( () -> "Result: " + responseBody );
-                return responseBody;
-            }
-            else
-            {
-                LOGGER.error( "Failed to get shorter URL: " + httpResponse.getStatusLine().getReasonPhrase() );
-            }
+            final String responseBody = httpResponse.getBody();
+            LOGGER.debug( () -> "Result: " + responseBody );
+            return responseBody;
         }
-        catch ( java.io.IOException e )
+        else
         {
-            LOGGER.error( "IOException: " + e.getMessage() );
+            LOGGER.error( "Failed to get shorter URL: " + httpResponse.getStatusPhrase() );
         }
         return input;
     }

+ 6 - 7
server/src/main/java/password/pwm/svc/telemetry/HttpTelemetrySender.java

@@ -30,12 +30,11 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.X509Utils;
 
 import java.io.Serializable;
 import java.util.HashMap;
@@ -61,9 +60,9 @@ public class HttpTelemetrySender implements TelemetrySender
             throws PwmUnrecoverableException
     {
         final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
-                .trustManager( new X509Utils.PromiscuousTrustManager( SessionLabel.TELEMETRY_SESSION_LABEL ) )
+                .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous )
                 .build();
-        final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmApplication, SessionLabel.TELEMETRY_SESSION_LABEL, pwmHttpClientConfiguration );
+        final PwmHttpClient pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( pwmHttpClientConfiguration );
         final String body = JsonUtil.serialize( statsPublishBean );
         final Map<String, String> headers = new HashMap<>();
         headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.json.getHeaderValueWithEncoding() );
@@ -76,7 +75,7 @@ public class HttpTelemetrySender implements TelemetrySender
                 .build();
 
         LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "preparing to send telemetry data to '" + settings.getUrl() + ")" );
-        pwmHttpClient.makeRequest( pwmHttpClientRequest );
+        pwmHttpClient.makeRequest( pwmHttpClientRequest, SessionLabel.TELEMETRY_SESSION_LABEL );
         LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "sent telemetry data to '" + settings.getUrl() + ")" );
     }
 

+ 4 - 5
server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java

@@ -29,8 +29,8 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
 import password.pwm.util.java.ConditionalTaskExecutor;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
@@ -38,7 +38,6 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.ChecksumInputStream;
-import password.pwm.util.secure.X509Utils;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -80,9 +79,9 @@ class WordlistSource
             {
                 final boolean promiscuous = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_CLIENT_PROMISCUOUS_WORDLIST_ENABLE ) );
                 final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
-                        .trustManager( promiscuous ? new X509Utils.PromiscuousTrustManager( null ) : null )
+                        .trustManagerType( promiscuous ? PwmHttpClientConfiguration.TrustManagerType.promiscuous : PwmHttpClientConfiguration.TrustManagerType.defaultJava )
                         .build();
-                final PwmHttpClient client = new PwmHttpClient( pwmApplication, null, pwmHttpClientConfiguration );
+                final PwmHttpClient client = pwmApplication.getHttpClientService().getPwmHttpClient( pwmHttpClientConfiguration );
                 return client.streamForUrl( wordlistConfiguration.getAutoImportUrl() );
             }
 

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

@@ -39,9 +39,9 @@ import password.pwm.http.PwmHttpResponseWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmURL;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.stats.Statistic;
@@ -133,8 +133,8 @@ public class CaptchaUtility
                     .build();
 
             LOGGER.debug( pwmRequest, () -> "sending reCaptcha verification request" );
-            final PwmHttpClient client = new PwmHttpClient( pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel() );
-            final PwmHttpClientResponse clientResponse = client.makeRequest( clientRequest );
+            final PwmHttpClient client = pwmRequest.getPwmApplication().getHttpClientService().getPwmHttpClient();
+            final PwmHttpClientResponse clientResponse = client.makeRequest( clientRequest, pwmRequest.getSessionLabel()  );
 
             if ( clientResponse.getStatusCode() != HttpServletResponse.SC_OK )
             {

+ 8 - 7
server/src/main/java/password/pwm/util/operations/ActionExecutor.java

@@ -34,10 +34,10 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.StringUtil;
@@ -212,17 +212,18 @@ public class ActionExecutor
                 if ( webAction.getCertificates() != null )
                 {
                     final PwmHttpClientConfiguration clientConfiguration = PwmHttpClientConfiguration.builder()
+                            .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
                             .certificates( webAction.getCertificates() )
                             .build();
 
-                    client = new PwmHttpClient( pwmApplication, sessionLabel, clientConfiguration );
+                    client = pwmApplication.getHttpClientService().getPwmHttpClient( clientConfiguration );
                 }
                 else
                 {
-                    client = new PwmHttpClient( pwmApplication, sessionLabel );
+                    client = pwmApplication.getHttpClientService().getPwmHttpClient( );
                 }
             }
-            final PwmHttpClientResponse clientResponse = client.makeRequest( clientRequest );
+            final PwmHttpClientResponse clientResponse = client.makeRequest( clientRequest, sessionLabel );
 
             final List<Integer> successStatus = webAction.getSuccessStatus() == null
                     ? Collections.emptyList()

+ 8 - 7
server/src/main/java/password/pwm/util/queue/SmsQueueManager.java

@@ -36,10 +36,10 @@ import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
@@ -501,21 +501,22 @@ public class SmsQueueManager implements PwmService
             {
                 if ( JavaHelper.isEmpty( config.readSettingAsCertificate( PwmSetting.SMS_GATEWAY_CERTIFICATES ) ) )
                 {
-                    pwmHttpClient = new PwmHttpClient( pwmApplication, sessionLabel );
+                    pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( );
                 }
                 else
                 {
                     final PwmHttpClientConfiguration clientConfiguration = PwmHttpClientConfiguration.builder()
+                            .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
                             .certificates( config.readSettingAsCertificate( PwmSetting.SMS_GATEWAY_CERTIFICATES ) )
                             .build();
 
-                    pwmHttpClient = new PwmHttpClient( pwmApplication, sessionLabel, clientConfiguration );
+                    pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( clientConfiguration );
                 }
             }
 
             try
             {
-                final PwmHttpClientResponse pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+                final PwmHttpClientResponse pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest, sessionLabel );
                 final int resultCode = pwmHttpClientResponse.getStatusCode();
 
                 final String responseBody = pwmHttpClientResponse.getBody();

+ 21 - 29
server/src/main/java/password/pwm/util/secure/X509Utils.java

@@ -33,9 +33,9 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmURL;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
@@ -92,7 +92,7 @@ public abstract class X509Utils
             throws PwmOperationalException
     {
         LOGGER.debug( () -> "ServerCertReader: beginning certificate read procedure to import certificates from host=" + host + ", port=" + port );
-        final CertReaderTrustManager certReaderTm = new CertReaderTrustManager( readCertificateFlagsFromConfig( configuration ) );
+        final CertReaderTrustManager certReaderTm = new CertReaderTrustManager( new PromiscuousTrustManager(), readCertificateFlagsFromConfig( configuration ) );
         try
         {
             // use custom trust manager to read the certificates
@@ -154,11 +154,10 @@ public abstract class X509Utils
     )
             throws PwmUnrecoverableException
     {
-        final CertReaderTrustManager certReaderTrustManager = new CertReaderTrustManager( readCertificateFlagsFromConfig( configuration ) );
         final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
-                .trustManager( certReaderTrustManager )
+                .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuousCertReader )
                 .build();
-        final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmApplication, sessionLabel, pwmHttpClientConfiguration );
+        final PwmHttpClient pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( pwmHttpClientConfiguration );
         final PwmHttpClientRequest request = PwmHttpClientRequest.builder()
                 .method( HttpMethod.GET )
                 .url( uri.toString() )
@@ -169,16 +168,16 @@ public abstract class X509Utils
         ErrorInformation requestError = null;
         try
         {
-            pwmHttpClient.makeRequest( request );
+            pwmHttpClient.makeRequest( request, sessionLabel );
         }
         catch ( PwmException e )
         {
             requestError = e.getErrorInformation();
         }
 
-        if ( certReaderTrustManager.getCertificates() != null )
+        if ( pwmHttpClient.readServerCertificates() != null )
         {
-            return certReaderTrustManager.getCertificates();
+            return pwmHttpClient.readServerCertificates();
         }
 
         {
@@ -235,54 +234,47 @@ public abstract class X509Utils
     public static class CertReaderTrustManager implements X509TrustManager
     {
         private final ReadCertificateFlag[] readCertificateFlags;
+        private final X509TrustManager wrappedTrustManager;
 
         private List<X509Certificate> certificates = new ArrayList<>();
 
-        public CertReaderTrustManager( final ReadCertificateFlag... readCertificateFlags )
+        public CertReaderTrustManager( final X509TrustManager wrappedTrustManager, final ReadCertificateFlag... readCertificateFlags )
         {
             this.readCertificateFlags = readCertificateFlags;
+            this.wrappedTrustManager = wrappedTrustManager;
         }
 
         public void checkClientTrusted( final X509Certificate[] chain, final String authType )
                 throws CertificateException
         {
-            LOGGER.debug( () -> "clientCheckTrusted invoked in CertReaderTrustManager" );
+            wrappedTrustManager.checkClientTrusted(  chain, authType );
         }
 
         public X509Certificate[] getAcceptedIssuers( )
         {
-            return null;
+            return wrappedTrustManager.getAcceptedIssuers();
         }
 
         public void checkServerTrusted( final X509Certificate[] chain, final String authType )
                 throws CertificateException
         {
             final List<X509Certificate> asList = Arrays.asList( chain );
-            certificates.addAll( asList );
-            final List<Map<String, String>> certDebugInfo = X509Utils.makeDebugInfoMap( certificates );
-            LOGGER.debug( () -> "read certificates from remote server: "
-                    + JsonUtil.serialize( new ArrayList<>( certDebugInfo ) ) );
+            certificates.addAll( JavaHelper.enumArrayContainsValue( readCertificateFlags, ReadCertificateFlag.ReadOnlyRootCA )
+                    ? identifyRootCACertificate( certificates )
+                    : asList );
+            wrappedTrustManager.checkServerTrusted( chain, authType );
         }
 
         public List<X509Certificate> getCertificates( )
         {
-            if ( JavaHelper.enumArrayContainsValue( readCertificateFlags, ReadCertificateFlag.ReadOnlyRootCA ) )
-            {
-                return Collections.unmodifiableList( identifyRootCACertificate( certificates ) );
-            }
+            LOGGER.debug( () -> "read certificates from remote server: "
+                    + JsonUtil.serialize( new ArrayList<>( X509Utils.makeDebugInfoMap( certificates ) ) ) );
             return Collections.unmodifiableList( certificates );
         }
     }
 
     public static class PromiscuousTrustManager implements X509TrustManager
     {
-        private final SessionLabel sessionLabel;
-
-        public PromiscuousTrustManager( final SessionLabel sessionLabel )
-        {
-            this.sessionLabel = sessionLabel;
-        }
-
         public X509Certificate[] getAcceptedIssuers( )
         {
             return new X509Certificate[ 0 ];
@@ -306,7 +298,7 @@ public abstract class X509Utils
                 {
                     try
                     {
-                        LOGGER.debug( sessionLabel, () -> "promiscuous trusting certificate during authType=" + authType + ", subject=" + cert.getSubjectDN().toString() );
+                        LOGGER.debug( () -> "promiscuous trusting certificate during authType=" + authType + ", subject=" + cert.getSubjectDN().toString() );
                     }
                     catch ( Exception e )
                     {

+ 8 - 9
server/src/main/java/password/pwm/ws/client/rest/RestClientHelper.java

@@ -30,13 +30,12 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.X509Utils;
 
 import java.util.LinkedHashMap;
 import java.util.Locale;
@@ -55,9 +54,9 @@ public class RestClientHelper
             throws PwmOperationalException, PwmUnrecoverableException
     {
         final PwmHttpClientConfiguration clientConfig = PwmHttpClientConfiguration.builder()
-                .trustManager( new X509Utils.PromiscuousTrustManager( SessionLabel.SYSTEM_LABEL ) )
+                .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous )
                 .build();
-        final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmApplication, SessionLabel.SYSTEM_LABEL, clientConfig );
+        final PwmHttpClient pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( clientConfig );
 
         final Map<String, String> httpPost = new LinkedHashMap<>();
         if ( locale != null )
@@ -79,9 +78,9 @@ public class RestClientHelper
         try
         {
             LOGGER.debug( () -> "beginning external rest call to: " + url + ", body: " + jsonRequestBody );
-            httpResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+            httpResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest, SessionLabel.SYSTEM_LABEL  );
             final String responseBody = httpResponse.getBody();
-            LOGGER.trace( () -> "external rest call returned: " + httpResponse.getStatusPhrase().toString()  );
+            LOGGER.trace( () -> "external rest call returned: " + httpResponse.getStatusPhrase()  );
             if ( httpResponse.getStatusCode() != 200 )
             {
                 final String errorMsg = "received non-200 response code (" + httpResponse.getStatusCode() + ") when executing web-service";

+ 7 - 6
server/src/main/java/password/pwm/ws/client/rest/form/RestFormDataClient.java

@@ -32,10 +32,10 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JsonUtil;
@@ -114,7 +114,7 @@ public class RestFormDataClient
         final PwmHttpClientResponse httpResponse;
         try
         {
-            httpResponse = getHttpClient( pwmApplication.getConfig() ).makeRequest( pwmHttpClientRequest );
+            httpResponse = getHttpClient( pwmApplication.getConfig() ).makeRequest( pwmHttpClientRequest, sessionLabel );
             final String responseBody = httpResponse.getBody();
             LOGGER.trace( () -> "external rest call returned: " + httpResponse.getStatusPhrase() + ", body: " + responseBody );
             if ( httpResponse.getStatusCode() != 200 )
@@ -142,9 +142,10 @@ public class RestFormDataClient
         final List<X509Certificate> certificates = remoteWebServiceConfiguration.getCertificates();
 
         final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
+                .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
                 .certificates( certificates )
                 .build();
-        return new PwmHttpClient( pwmApplication, null, pwmHttpClientConfiguration );
+        return pwmApplication.getHttpClientService().getPwmHttpClient( pwmHttpClientConfiguration );
     }
 
 }

+ 70 - 40
server/src/test/java/password/pwm/http/client/PwmHttpClientTest.java

@@ -23,24 +23,27 @@ package password.pwm.http.client;
 import com.github.tomakehurst.wiremock.client.WireMock;
 import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import org.apache.commons.io.IOUtils;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
 import org.apache.http.entity.ContentType;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.mockito.Mockito;
 import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpHeader;
+import password.pwm.http.HttpMethod;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
+import password.pwm.util.localdb.TestHelper;
 
-import javax.net.ssl.SSLHandshakeException;
 import java.io.InputStream;
 import java.security.KeyStore;
 import java.security.cert.X509Certificate;
@@ -54,6 +57,10 @@ public class PwmHttpClientTest
             .dynamicPort()
             .dynamicHttpsPort() );
 
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+
     // Create a few mock objects, in case they're needed by the tests
     private Configuration configuration = Mockito.spy( new Configuration( StoredConfigurationImpl.newStoredConfiguration() ) );
 
@@ -73,29 +80,31 @@ public class PwmHttpClientTest
                         .withHeader( "Content-Type", "text/plain" )
                         .withBody( "PwmAbout from the local mock server" ) ) );
 
-        // Obtain the HTTP client from PWM
-        final HttpClient httpClient = PwmHttpClient.getHttpClient( configuration );
+        // Obtain the HTTP client
+        final PwmApplication pwmApplication = TestHelper.makeTestPwmApplication( temporaryFolder.newFolder() );
+        final PwmHttpClient httpClient = pwmApplication.getHttpClientService().getPwmHttpClient(  );
 
         // Execute the HTTP request
-        final HttpGet httpGet = new HttpGet( String.format( "http://localhost:%d/simpleHello", wireMockRule.port() ) );
-        final HttpResponse response = httpClient.execute( httpGet );
+        final String url = String.format( "http://localhost:%d/simpleHello", wireMockRule.port() );
+        final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder().method( HttpMethod.GET ).url( url ).build();
+        final PwmHttpClientResponse response = httpClient.makeRequest( pwmHttpClientRequest, null );
 
         // Verify the response
-        final int responseStatusCode = response.getStatusLine().getStatusCode();
+        final int responseStatusCode = response.getStatusCode();
         Assert.assertEquals( responseStatusCode, 200 );
 
-        final String responseContent = IOUtils.toString( response.getEntity().getContent() );
+        final String responseContent = response.getBody();
         Assert.assertTrue( responseContent.startsWith( "PwmAbout" ) );
 
         // Verify the HTTP server got called as expected
         wireMockRule.verify( WireMock.getRequestedFor( WireMock.urlEqualTo( "/simpleHello" ) )
-                .withHeader( "User-Agent", WireMock.equalTo( PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION ) ) );
+                .withHeader( "User-Agent", WireMock.equalTo( PwmConstants.PWM_APP_NAME ) ) );
     }
 
     /**
      * Test making an SSL request without setting SECURITY_HTTP_PROMISCUOUS_ENABLE to true, or supplying any certificates.
      */
-    @Test( expected = SSLHandshakeException.class )
+    @Test( expected = PwmUnrecoverableException.class )
     public void testGetHttpClientSslHelloFail() throws Exception
     {
         // Stub out our local HTTP server
@@ -104,12 +113,17 @@ public class PwmHttpClientTest
                         .withHeader( "Content-Type", "text/plain" )
                         .withBody( "PwmAbout from the local mock server" ) ) );
 
-        final HttpClient httpClient = PwmHttpClient.getHttpClient( configuration );
+        // Obtain the HTTP client
+        final PwmApplication pwmApplication = TestHelper.makeTestPwmApplication( temporaryFolder.newFolder() );
+        final PwmHttpClient httpClient = pwmApplication.getHttpClientService().getPwmHttpClient(  );
+
+        // Execute the HTTP request
+        final String url = String.format( "https://localhost:%d/simpleHello", wireMockRule.httpsPort() );
+        final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder().method( HttpMethod.GET ).url( url ).build();
 
-        final HttpGet httpGet = new HttpGet( String.format( "https://localhost:%d/simpleHello", wireMockRule.httpsPort() ) );
 
         // This should throw an exception, since we're doing https without setting SECURITY_HTTP_PROMISCUOUS_ENABLE, or setting certificates
-        httpClient.execute( httpGet );
+        httpClient.makeRequest( pwmHttpClientRequest, null );
     }
 
     /**
@@ -121,28 +135,34 @@ public class PwmHttpClientTest
         // Stub out our local HTTP server
         wireMockRule.stubFor( WireMock.get( WireMock.urlEqualTo( "/simpleHello" ) )
                 .willReturn( WireMock.aResponse()
-                        .withHeader( "Content-Type", "text/plain" )
+                        .withHeader( HttpHeader.ContentType.getHttpName(), "text/plain" )
                         .withBody( "PwmAbout from the local mock server" ) ) );
 
         // Stub out some mock object behavior
         Mockito.when( configuration.readAppProperty( AppProperty.SECURITY_HTTP_PROMISCUOUS_ENABLE ) ).thenReturn( "true" );
 
-        final HttpClient httpClient = PwmHttpClient.getHttpClient( configuration );
+        // Obtain the HTTP client
+        final PwmApplication pwmApplication = TestHelper.makeTestPwmApplication( temporaryFolder.newFolder() );
+        final PwmHttpClient httpClient = pwmApplication.getHttpClientService().getPwmHttpClient(
+                PwmHttpClientConfiguration.builder().trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous ).build()
+        );
 
-        final HttpGet httpGet = new HttpGet( String.format( "https://localhost:%d/simpleHello", wireMockRule.httpsPort() ) );
-        final HttpResponse response = httpClient.execute( httpGet );
+        // Execute the HTTP request
+        final String url = String.format( "https://localhost:%d/simpleHello", wireMockRule.httpsPort() );
+        final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder().method( HttpMethod.GET ).url( url ).build();
+        final PwmHttpClientResponse response = httpClient.makeRequest( pwmHttpClientRequest, null );
 
-        // Verify the response
-        final int responseStatusCode = response.getStatusLine().getStatusCode();
+        final int responseStatusCode = response.getStatusCode();
         Assert.assertEquals( responseStatusCode, 200 );
 
-        final String responseContent = IOUtils.toString( response.getEntity().getContent() );
+        final String responseContent = response.getBody();
         Assert.assertTrue( responseContent.startsWith( "PwmAbout" ) );
     }
 
     /**
      * Test making an SSL request using the server's certificate.
      */
+
     @Test
     public void testGetHttpClientSslWithCertificates() throws Exception
     {
@@ -152,23 +172,25 @@ public class PwmHttpClientTest
                         .withHeader( HttpHeader.ContentType.getHttpName(), ContentType.TEXT_PLAIN.getMimeType() )
                         .withBody( "PwmAbout from the local mock server" ) ) );
 
-        final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
-                .certificates( getWireMockSelfSignedCertificate() )
-                .build();
-
         Mockito.when( configuration.readAppProperty( AppProperty.HTTP_CLIENT_ENABLE_HOSTNAME_VERIFICATION ) ).thenReturn( "false" );
 
+        // Obtain the HTTP client
+        final PwmApplication pwmApplication = TestHelper.makeTestPwmApplication( temporaryFolder.newFolder(), configuration );
+        final PwmHttpClient httpClient = pwmApplication.getHttpClientService().getPwmHttpClient(
+                PwmHttpClientConfiguration.builder().trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
+                        .certificates( getWireMockSelfSignedCertificate() ).build()
+        );
 
-        final HttpClient httpClient = PwmHttpClient.getHttpClient( configuration, pwmHttpClientConfiguration, null  );
-
-        final HttpGet httpGet = new HttpGet( String.format( "https://localhost:%d/simpleHello", wireMockRule.httpsPort() ) );
-        final HttpResponse response = httpClient.execute( httpGet );
+        // Execute the HTTP request
+        final String url = String.format( "https://localhost:%d/simpleHello", wireMockRule.httpsPort() );
+        final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder().method( HttpMethod.GET ).url( url ).build();
+        final PwmHttpClientResponse response = httpClient.makeRequest( pwmHttpClientRequest, null );
 
         // Verify the response
-        final int responseStatusCode = response.getStatusLine().getStatusCode();
+        final int responseStatusCode = response.getStatusCode();
         Assert.assertEquals( responseStatusCode, 200 );
 
-        final String responseContent = IOUtils.toString( response.getEntity().getContent() );
+        final String responseContent = response.getBody();
         Assert.assertTrue( responseContent.startsWith( "PwmAbout" ) );
     }
 
@@ -185,19 +207,27 @@ public class PwmHttpClientTest
                         .withBody( "PwmAbout from the local mock server" ) ) );
 
         // Stub out some mock object behavior
-        Mockito.when( configuration.readSettingAsString( PwmSetting.HTTP_PROXY_URL ) ).thenReturn( String.format( "http://localhost:%d/simpleHello", wireMockRule.port() ) );
+        Mockito.when( configuration.readSettingAsString( PwmSetting.HTTP_PROXY_URL ) )
+                .thenReturn( String.format( "http://localhost:%d/simpleHello", wireMockRule.port() ) );
+
+        // Obtain the HTTP client
+        final PwmApplication pwmApplication = TestHelper.makeTestPwmApplication( temporaryFolder.newFolder(), configuration );
+        final PwmHttpClient httpClient = pwmApplication.getHttpClientService().getPwmHttpClient(
+                PwmHttpClientConfiguration.builder().trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
+                        .certificates( getWireMockSelfSignedCertificate() ).build()
+        );
 
-        final HttpClient httpClient = PwmHttpClient.getHttpClient( configuration );
 
-        // We are making a request to www.microfocus.com, but our server on localhost will receive it
-        final HttpGet httpGet = new HttpGet( "http://www.microfocus.com/simpleHello" );
-        final HttpResponse response = httpClient.execute( httpGet );
+        // We are making a request to www.example.com, but our server on localhost will receive it
+        final String url = "http://www.example.com/simpleHello";
+        final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder().method( HttpMethod.GET ).url( url ).build();
+        final PwmHttpClientResponse response = httpClient.makeRequest( pwmHttpClientRequest, null );
 
         // Verify the response
-        final int responseStatusCode = response.getStatusLine().getStatusCode();
+        final int responseStatusCode = response.getStatusCode();
         Assert.assertEquals( responseStatusCode, 200 );
 
-        final String responseContent = IOUtils.toString( response.getEntity().getContent() );
+        final String responseContent = response.getBody();
         Assert.assertTrue( responseContent.startsWith( "PwmAbout" ) );
     }
 

+ 7 - 1
server/src/test/java/password/pwm/util/localdb/TestHelper.java

@@ -58,10 +58,16 @@ public class TestHelper
     public static PwmApplication makeTestPwmApplication( final File tempFolder )
             throws PwmUnrecoverableException
     {
-        Logger.getRootLogger().setLevel( Level.OFF );
         final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.newStoredConfiguration();
         storedConfiguration.writeSetting( PwmSetting.EVENTS_JAVA_STDOUT_LEVEL, new StringValue( PwmLogLevel.FATAL.toString() ), null );
         final Configuration configuration = new Configuration( storedConfiguration );
+        return makeTestPwmApplication( tempFolder, configuration );
+    }
+
+    public static PwmApplication makeTestPwmApplication( final File tempFolder, final Configuration configuration )
+            throws PwmUnrecoverableException
+    {
+        Logger.getRootLogger().setLevel( Level.OFF );
         final PwmEnvironment pwmEnvironment = new PwmEnvironment.Builder( configuration, tempFolder )
                 .setApplicationMode( PwmApplicationMode.READ_ONLY )
                 .setInternalRuntimeInstance( true )