All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensaml.soap.client.http.AbstractPipelineHttpSOAPClient Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * Licensed to the University Corporation for Advanced Internet Development,
 * Inc. (UCAID) under one or more contributor license agreements.  See the
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID licenses this file to You 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 org.opensaml.soap.client.http;

import static org.opensaml.security.httpclient.HttpClientSecurityConstants.CONTEXT_KEY_CRITERIA_SET;
import static org.opensaml.security.httpclient.HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE;
import static org.opensaml.security.httpclient.HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL;

import java.io.IOException;
import java.util.function.Function;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.net.ssl.SSLException;

import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.opensaml.messaging.context.InOutOperationContext;
import org.opensaml.messaging.context.httpclient.HttpClientRequestContext;
import org.opensaml.messaging.decoder.MessageDecodingException;
import org.opensaml.messaging.decoder.httpclient.HttpClientResponseMessageDecoder;
import org.opensaml.messaging.encoder.MessageEncodingException;
import org.opensaml.messaging.encoder.httpclient.HttpClientRequestMessageEncoder;
import org.opensaml.messaging.handler.MessageHandlerException;
import org.opensaml.messaging.pipeline.httpclient.HttpClientMessagePipeline;
import org.opensaml.security.SecurityException;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.httpclient.HttpClientSecurityParameters;
import org.opensaml.security.httpclient.HttpClientSecuritySupport;
import org.opensaml.security.messaging.HttpClientSecurityContext;
import org.opensaml.soap.client.SOAPClient;
import org.opensaml.soap.client.SOAPClientContext;
import org.opensaml.soap.client.SOAPFaultException;
import org.opensaml.soap.common.SOAP11FaultDecodingException;
import org.opensaml.soap.common.SOAPException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * SOAP client that is based on {@link HttpClientMessagePipeline}.
 */
@ThreadSafe
public abstract class AbstractPipelineHttpSOAPClient 
        extends AbstractInitializableComponent implements SOAPClient {

    /** Class logger. */
    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractPipelineHttpSOAPClient.class);

    /** HTTP client used to send requests and receive responses. */
    @NonnullAfterInit private HttpClient httpClient;
    
    /** HTTP client security parameters. */
    @Nullable private HttpClientSecurityParameters httpClientSecurityParameters;
    
    /** Strategy for building the criteria set which is input to the TLS trust engine. */
    @Nullable private Function tlsCriteriaSetStrategy;
    
    /** Constructor. */
    public AbstractPipelineHttpSOAPClient() {
        super();
    }

    /** {@inheritDoc} */
    @Override
    protected void doInitialize() throws ComponentInitializationException {
        super.doInitialize();
        
        if (httpClient == null) {
            throw new ComponentInitializationException("HttpClient cannot be null");
        } 
    }
    
    /** {@inheritDoc} */
    @Override
    protected void doDestroy() {
        httpClient = null;
        httpClientSecurityParameters = null;
        tlsCriteriaSetStrategy = null;
        
        super.doDestroy();
    }
    
    /**
     * Get the client used to make outbound HTTP requests.
     * 
     * @return the client instance
     */
    @Nonnull public HttpClient getHttpClient() {
        return httpClient;
    }

    /**
     * Set the client used to make outbound HTTP requests.
     * 
     * 

This client SHOULD employ a thread-safe {@link HttpClient} and may be shared with other objects.

* * @param client client object */ public void setHttpClient(@Nonnull final HttpClient client) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); ComponentSupport.ifDestroyedThrowDestroyedComponentException(this); httpClient = Constraint.isNotNull(client, "HttpClient cannot be null"); } /** * Get the optional client security parameters. * * @return the client security parameters, or null */ @Nullable public HttpClientSecurityParameters getHttpClientSecurityParameters() { return httpClientSecurityParameters; } /** * Set the optional client security parameters. * * @param params the new client security parameters */ public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); ComponentSupport.ifDestroyedThrowDestroyedComponentException(this); httpClientSecurityParameters = params; } /** * Get the strategy function which builds the dynamically-populated criteria set which is * input to the TLS TrustEngine, if no static criteria set is supplied either via context * or locally-configured {@link HttpClientSecurityParameters}. * * @return the strategy function, or null */ @Nullable public Function getTLSCriteriaSetStrategy() { return tlsCriteriaSetStrategy; } /** * Set the strategy function which builds the dynamically-populated criteria set which is * input to the TLS TrustEngine, if no static criteria set is supplied either via context * or locally-configured {@link HttpClientSecurityParameters}. * * @param function the strategy function, or null */ public void setTLSCriteriaSetStrategy(@Nullable final Function function) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); ComponentSupport.ifDestroyedThrowDestroyedComponentException(this); tlsCriteriaSetStrategy = function; } /** {@inheritDoc} */ // Checkstyle: CyclomaticComplexity|MethodLength OFF public void send(@Nonnull @NotEmpty final String endpoint, @Nonnull final InOutOperationContext operationContext) throws SOAPException, SecurityException { Constraint.isNotNull(endpoint, "Endpoint cannot be null"); Constraint.isNotNull(operationContext, "Operation context cannot be null"); HttpClientMessagePipeline pipeline = null; try { // Store the endpoint URI operationContext.getSubcontext(SOAPClientContext.class, true).setDestinationURI(endpoint); // Pipeline resolution pipeline = resolvePipeline(operationContext); // Outbound payload handling if (pipeline.getOutboundPayloadMessageHandler() != null) { pipeline.getOutboundPayloadMessageHandler().invoke(operationContext.getOutboundMessageContext()); } final HttpUriRequest httpRequest = buildHttpRequest(endpoint, operationContext); // Request encoding + outbound transport handling final HttpClientRequestMessageEncoder encoder = pipeline.getEncoder(); encoder.setHttpRequest(httpRequest); encoder.setMessageContext(operationContext.getOutboundMessageContext()); encoder.initialize(); encoder.prepareContext(); if (pipeline.getOutboundTransportMessageHandler() != null) { pipeline.getOutboundTransportMessageHandler().invoke(operationContext.getOutboundMessageContext()); } encoder.encode(); // HttpClient execution final HttpClientContext httpContext = buildHttpContext(httpRequest, operationContext); final HttpResponse httpResponse = getHttpClient().execute(httpRequest, httpContext); HttpClientSecuritySupport.checkTLSCredentialEvaluated(httpContext, httpRequest.getURI().getScheme()); // Response decoding final HttpClientResponseMessageDecoder decoder = pipeline.getDecoder(); decoder.setHttpResponse(httpResponse); decoder.initialize(); decoder.decode(); operationContext.setInboundMessageContext(decoder.getMessageContext()); // Inbound message handling if (pipeline.getInboundMessageHandler() != null) { pipeline.getInboundMessageHandler().invoke(operationContext.getInboundMessageContext()); } } catch (final SOAP11FaultDecodingException e) { final SOAPFaultException faultException = new SOAPFaultException(e.getMessage(), e); faultException.setFault(e.getFault()); throw faultException; } catch (final SSLException e) { throw new SecurityException("Problem establising TLS connection to: " + endpoint, e); } catch (final ComponentInitializationException e) { throw new SOAPException("Problem initializing a SOAP client component", e); } catch (final MessageEncodingException e) { throw new SOAPException("Problem encoding SOAP request message to: " + endpoint, e); } catch (final MessageDecodingException e) { throw new SOAPException("Problem decoding SOAP response message from: " + endpoint, e); } catch (final MessageHandlerException e) { throw new SOAPException("Problem handling SOAP message exchange with: " + endpoint, e); } catch (final ClientProtocolException e) { throw new SOAPException("Client protocol problem sending SOAP request message to: " + endpoint, e); } catch (final IOException e) { throw new SOAPException("I/O problem with SOAP message exchange with: " + endpoint, e); } finally { if (pipeline != null) { pipeline.getEncoder().destroy(); pipeline.getDecoder().destroy(); } } } // Checkstyle: CyclomaticComplexity|MethodLength ON /** * Resolve and return a new instance of the {@link HttpClientMessagePipeline} to be processed. * *

* Each call to this (factory) method MUST produce a new instance of the pipeline. *

* *

* The default behavior is to simply call {@link #newPipeline()}. *

* * @param operationContext the current operation context * * @return a new pipeline instance * * @throws SOAPException if there is an error obtaining a new pipeline instance */ @Nonnull protected HttpClientMessagePipeline resolvePipeline(@Nonnull final InOutOperationContext operationContext) throws SOAPException { try { return newPipeline(); } catch (final SOAPException e) { log.warn("Problem resolving pipeline instance: {}", e.getMessage()); throw e; } catch (final Exception e) { // This is to handle RuntimeExceptions, for example thrown by Spring dynamic factory approaches log.warn("Problem resolving pipeline instance: {}", e.getMessage()); throw new SOAPException("Could not resolve pipeline", e); } } /** * Get a new instance of the {@link HttpClientMessagePipeline} to be processed. * *

* Each call to this (factory) method MUST produce a new instance of the pipeline. *

* * @return the new pipeline instance * * @throws SOAPException if there is an error obtaining a new pipeline instance */ @Nonnull protected abstract HttpClientMessagePipeline newPipeline() throws SOAPException; /** * Build the {@link HttpUriRequest} instance to be executed by the HttpClient. * * @param endpoint the endpoint to which the message will be sent * @param operationContext the current operation context * @return the HTTP request to be executed */ @Nonnull protected HttpUriRequest buildHttpRequest(@Nonnull @NotEmpty final String endpoint, @Nonnull final InOutOperationContext operationContext) { return new HttpPost(endpoint); } /** * Build the {@link HttpClientContext} instance to be used by the HttpClient. * * @param request the HTTP client request * @param operationContext the current operation context * @return the client context instance */ @Nonnull protected HttpClientContext buildHttpContext(@Nonnull final HttpUriRequest request, @Nonnull final InOutOperationContext operationContext) { final HttpClientContext clientContext = resolveClientContext(operationContext); final HttpClientSecurityParameters contextSecurityParameters = resolveContextSecurityParameters(operationContext); HttpClientSecuritySupport.marshalSecurityParameters(clientContext, contextSecurityParameters, false); HttpClientSecuritySupport.marshalSecurityParameters(clientContext, getHttpClientSecurityParameters(), false); if ("https".equalsIgnoreCase(request.getURI().getScheme()) && clientContext.getAttribute(CONTEXT_KEY_TRUST_ENGINE) != null) { if (clientContext.getAttribute(CONTEXT_KEY_CRITERIA_SET) == null) { clientContext.setAttribute(CONTEXT_KEY_CRITERIA_SET, buildTLSCriteriaSet(request, operationContext)); } // Default this false if not explicitly set, as pipeline handlers will generally // want to evaluate the result themselves. Can set explicitly on this client's params // instance if want to override. if (clientContext.getAttribute(CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL) == null) { clientContext.setAttribute(CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL, Boolean.FALSE); } } HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(clientContext, request); return clientContext; } /** * Resolve the {@link HttpClientSecurityParameters} instance present in the current operation context. * *

* The default implementation returns the outbound subcontext value * {@link HttpClientSecurityContext#getSecurityParameters()}. *

* *

* Note that any values supplied via this instance will override those supplied locally via * {@link #setHttpClientSecurityParameters(HttpClientSecurityParameters)}. *

* * @param operationContext the current operation context * @return the client security parameters resolved from the current operation context, or null */ protected HttpClientSecurityParameters resolveContextSecurityParameters( @Nonnull final InOutOperationContext operationContext) { final HttpClientSecurityContext securityContext = operationContext.getOutboundMessageContext().getSubcontext(HttpClientSecurityContext.class); if (securityContext != null) { return securityContext.getSecurityParameters(); } return null; } /** * Resolve the effective {@link HttpClientContext} instance to use for the current request. * *

* The default implementation first attempts to resolve the outbound subcontext value * {@link HttpClientRequestContext#getHttpClientContext()}. If no context value is present, * a new empty context instance will be returned via {@link HttpClientContext#create()}. *

* *

* Note that any security-related attributes supplied directly the client context returned here * will override the corresponding values supplied via both operation context and locally-configured * instances of {@link HttpClientSecurityParameters}. *

* * @param operationContext the current operation context * @return the effective client context instance to use */ @Nonnull protected HttpClientContext resolveClientContext(@Nonnull final InOutOperationContext operationContext) { final HttpClientRequestContext requestContext = operationContext.getOutboundMessageContext().getSubcontext(HttpClientRequestContext.class, true); if (requestContext.getHttpClientContext() == null) { requestContext.setHttpClientContext(HttpClientContext.create()); } return requestContext.getHttpClientContext(); } /** * Build the dynamic {@link CriteriaSet} instance to be used for TLS trust evaluation. * * @param request the HTTP client request * @param operationContext the current operation context * @return the new criteria set instance */ @Nonnull protected CriteriaSet buildTLSCriteriaSet(@Nonnull final HttpUriRequest request, @Nonnull final InOutOperationContext operationContext) { final CriteriaSet criteriaSet = new CriteriaSet(); if (getTLSCriteriaSetStrategy() != null) { final CriteriaSet resolved = getTLSCriteriaSetStrategy().apply(operationContext); if (resolved != null) { criteriaSet.addAll(resolved); } } if (!criteriaSet.contains(UsageCriterion.class)) { criteriaSet.add(new UsageCriterion(UsageType.SIGNING)); } return criteriaSet; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy