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

org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver Maven / Gradle / Ivy

The 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.saml.metadata.resolver.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Timer;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.resolver.ResolverException;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.util.EntityUtils;
import org.opensaml.security.httpclient.HttpClientSecurityParameters;
import org.opensaml.security.httpclient.HttpClientSecuritySupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A metadata provider that pulls metadata using an HTTP GET. Metadata is cached until one of these criteria is met:
 * 
    *
  • The smallest cacheDuration within the metadata is exceeded
  • *
  • The earliest validUntil time within the metadata is exceeded
  • *
  • The maximum cache duration is exceeded
  • *
* * Metadata is filtered prior to determining the cache expiration data. This allows a filter to remove XMLObjects that * may effect the cache duration but for which the user of this provider does not care about. * * It is the responsibility of the caller to re-initialize, via {@link #initialize()}, if any properties of this * provider are changed. */ public class HTTPMetadataResolver extends AbstractReloadingMetadataResolver { /** Class logger. */ @Nonnull private final Logger log = LoggerFactory.getLogger(HTTPMetadataResolver.class); /** HTTP Client used to pull the metadata. */ private HttpClient httpClient; /** URL to the Metadata. */ private URI metadataURI; /** The ETag provided when the currently cached metadata was fetched. */ private String cachedMetadataETag; /** The Last-Modified information provided when the currently cached metadata was fetched. */ private String cachedMetadataLastModified; /** Optional HttpClient security parameters.*/ @Nullable private HttpClientSecurityParameters httpClientSecurityParameters; /** * Constructor. * * @param client HTTP client used to pull in remote metadata * @param metadataURL URL to the remove remote metadata * * @throws ResolverException thrown if the HTTP client is null or the metadata URL provided is invalid */ public HTTPMetadataResolver(final HttpClient client, final String metadataURL) throws ResolverException { this(null, client, metadataURL); } /** * Constructor. * * @param backgroundTaskTimer timer used to schedule background metadata refresh tasks * @param client HTTP client used to pull in remote metadata * @param metadataURL URL to the remove remote metadata * * @throws ResolverException thrown if the HTTP client is null or the metadata URL provided is invalid */ public HTTPMetadataResolver(final Timer backgroundTaskTimer, final HttpClient client, final String metadataURL) throws ResolverException { super(backgroundTaskTimer); if (client == null) { throw new ResolverException("HTTP client may not be null"); } httpClient = client; try { metadataURI = new URI(metadataURL); } catch (final URISyntaxException e) { throw new ResolverException("Illegal URL syntax", e); } } /** * Gets the URL to fetch the metadata. * * @return the URL to fetch the metadata */ public String getMetadataURI() { return metadataURI.toASCIIString(); } /** * Get the instance of {@link HttpClientSecurityParameters} which provides various parameters to influence * the security behavior of the HttpClient instance. * * @return the parameters instance, or null */ @Nullable protected HttpClientSecurityParameters getHttpClientSecurityParameters() { return httpClientSecurityParameters; } /** * Set an instance of {@link HttpClientSecurityParameters} which provides various parameters to influence * the security behavior of the HttpClient instance. * *

* For all TLS-related parameters, must be used in conjunction with an HttpClient instance * which is configured with either a: *

* *
    *
  • * a {@link net.shibboleth.utilities.java.support.httpclient.TLSSocketFactory} *
  • *
  • * {@link org.opensaml.security.httpclient.impl.SecurityEnhancedTLSSocketFactory} which wraps * an instance of {@link net.shibboleth.utilities.java.support.httpclient.TLSSocketFactory}, with * the latter likely configured in a "no trust" configuration. This variant is required if either a * trust engine or a client TLS credential is to be used. *
  • *
* *

* For convenience methods for building a * {@link net.shibboleth.utilities.java.support.httpclient.TLSSocketFactory}, * see {@link net.shibboleth.utilities.java.support.httpclient.HttpClientSupport}. *

* *

* If the appropriate TLS socket factory is not configured and a trust engine is specified, * then this will result in no TLS trust evaluation being performed and a * {@link ResolverException} will ultimately be thrown. *

* * @param params the security parameters */ public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) { ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this); ComponentSupport.ifDestroyedThrowDestroyedComponentException(this); httpClientSecurityParameters = params; } /** {@inheritDoc} */ @Override protected void doDestroy() { httpClient = null; httpClientSecurityParameters = null; metadataURI = null; cachedMetadataETag = null; cachedMetadataLastModified = null; super.doDestroy(); } /** {@inheritDoc} */ @Override protected String getMetadataIdentifier() { return metadataURI.toString(); } /** * Gets the metadata document from the remote server. * * @return the metadata from remote server, or null if the metadata document has not changed since the last * retrieval * * @throws ResolverException thrown if there is a problem retrieving the metadata from the remote server */ @Override protected byte[] fetchMetadata() throws ResolverException { final HttpGet httpGet = buildHttpGet(); final HttpClientContext context = buildHttpClientContext(httpGet); HttpResponse response = null; try { log.debug("{} Attempting to fetch metadata document from '{}'", getLogPrefix(), metadataURI); response = httpClient.execute(httpGet, context); HttpClientSecuritySupport.checkTLSCredentialEvaluated(context, metadataURI.getScheme()); final int httpStatusCode = response.getStatusLine().getStatusCode(); if (httpStatusCode == HttpStatus.SC_NOT_MODIFIED) { log.debug("{} Metadata document from '{}' has not changed since last retrieval", getLogPrefix(), getMetadataURI()); return null; } if (httpStatusCode != HttpStatus.SC_OK) { final String errMsg = "Non-ok status code " + httpStatusCode + " returned from remote metadata source " + metadataURI; log.error("{} " + errMsg, getLogPrefix()); throw new ResolverException(errMsg); } processConditionalRetrievalHeaders(response); final byte[] rawMetadata = getMetadataBytesFromResponse(response); log.debug("{} Successfully fetched {} bytes of metadata from {}", getLogPrefix(), rawMetadata.length, getMetadataURI()); return rawMetadata; } catch (final IOException e) { final String errMsg = "Error retrieving metadata from " + metadataURI; log.error("{} {}: {}", getLogPrefix(), errMsg, e.getMessage()); throw new ResolverException(errMsg, e); } finally { try { if (response != null && response instanceof CloseableHttpResponse) { ((CloseableHttpResponse) response).close(); } } catch (final IOException e) { log.error("{} Error closing HTTP response from {}", metadataURI, getLogPrefix(), e); } } } /** * Builds the {@link HttpGet} instance used to fetch the metadata. The returned method advertises support for GZIP * and deflate compression, enables conditional GETs if the cached metadata came with either an ETag or * Last-Modified information, and sets up basic authentication if such is configured. * * @return the constructed HttpGet instance */ protected HttpGet buildHttpGet() { final HttpGet getMethod = new HttpGet(getMetadataURI()); if (cachedMetadataETag != null) { getMethod.setHeader("If-None-Match", cachedMetadataETag); } if (cachedMetadataLastModified != null) { getMethod.setHeader("If-Modified-Since", cachedMetadataLastModified); } return getMethod; } /** * Build the {@link HttpClientContext} instance which will be used to invoke the {@link HttpClient} request. * * @param request the current HTTP request * * @return a new instance of {@link HttpClientContext} */ protected HttpClientContext buildHttpClientContext(@Nonnull final HttpUriRequest request) { // TODO Really request should be @Nonnull, change when we remove deprecated buildHttpClientContext() final HttpClientContext context = HttpClientContext.create(); HttpClientSecuritySupport.marshalSecurityParameters(context, httpClientSecurityParameters, true); HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(context, request); return context; } /** * Records the ETag and Last-Modified headers, from the response, if they are present. * * @param response GetMethod containing a valid HTTP response */ protected void processConditionalRetrievalHeaders(final HttpResponse response) { Header httpHeader = response.getFirstHeader("ETag"); if (httpHeader != null) { cachedMetadataETag = httpHeader.getValue(); } httpHeader = response.getFirstHeader("Last-Modified"); if (httpHeader != null) { cachedMetadataLastModified = httpHeader.getValue(); } } /** * Extracts the raw metadata bytes from the response taking in to account possible deflate and GZip compression. * * @param response GetMethod containing a valid HTTP response * * @return the raw metadata bytes * * @throws ResolverException thrown if there is a problem getting the raw metadata bytes from the response */ protected byte[] getMetadataBytesFromResponse(final HttpResponse response) throws ResolverException { log.debug("{} Attempting to extract metadata from response to request for metadata from '{}'", getLogPrefix(), getMetadataURI()); try { final InputStream ins = response.getEntity().getContent(); return inputstreamToByteArray(ins); } catch (final IOException e) { log.error("{} Unable to read response: {}", getLogPrefix(), e.getMessage()); throw new ResolverException("Unable to read response", e); } finally { // Make sure entity has been completely consumed. EntityUtils.consumeQuietly(response.getEntity()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy