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

org.opencastproject.kernel.security.TrustedHttpClientImpl Maven / Gradle / Ivy

There is a newer version: 16.7
Show newest version
/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.opencastproject.kernel.security;

import static org.opencastproject.kernel.rest.CurrentJobFilter.CURRENT_JOB_HEADER;
import static org.opencastproject.kernel.security.DelegatingAuthenticationEntryPoint.DIGEST_AUTH;
import static org.opencastproject.kernel.security.DelegatingAuthenticationEntryPoint.REQUESTED_AUTH_HEADER;
import static org.opencastproject.util.OsgiUtil.getOptContextProperty;

import org.opencastproject.kernel.http.api.HttpClient;
import org.opencastproject.kernel.http.impl.HttpClientFactory;
import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.SecurityConstants;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.TrustedHttpClient;
import org.opencastproject.security.api.TrustedHttpClientException;
import org.opencastproject.security.api.User;
import org.opencastproject.security.urlsigning.exception.UrlSigningException;
import org.opencastproject.security.urlsigning.service.UrlSigningService;
import org.opencastproject.security.util.HttpResponseWrapper;
import org.opencastproject.security.util.StandAloneTrustedHttpClientImpl;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.urlsigning.utils.ResourceRequestUtil;
import org.opencastproject.util.Log;
import org.opencastproject.util.data.Either;
import org.opencastproject.util.data.Function;

import com.entwinemedia.fn.data.Opt;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.params.CoreConnectionPNames;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

import javax.management.MBeanServer;
import javax.management.ObjectName;

/**
 * An http client that executes secure (though not necessarily encrypted) http requests.
 */
public class TrustedHttpClientImpl implements TrustedHttpClient, HttpConnectionMXBean {
  /** Header name used to request a new nonce from a server a request is sent to. */
  public static final String AUTHORIZATION_HEADER_NAME = "Authorization";

  /** The logger */
  private static final Logger logger = LoggerFactory.getLogger(TrustedHttpClientImpl.class);

  /** The configuration property specifying the digest authentication user */
  public static final String DIGEST_AUTH_USER_KEY = "org.opencastproject.security.digest.user";

  /** The configuration property specifying the digest authentication password */
  public static final String DIGEST_AUTH_PASS_KEY = "org.opencastproject.security.digest.pass";

  /** The configuration property specifying the number of times to retry after the nonce timesouts on a request. */
  public static final String NONCE_TIMEOUT_RETRY_KEY = "org.opencastproject.security.digest.nonce.retries";

  /** The configuration property specifying the duration a signed url will remain valid for. */
  protected static final String INTERNAL_URL_SIGNING_DURATION_KEY =
    "org.opencastproject.security.internal.url.signing.duration";

  /**
   * The configuration property specifying the minimum amount of time in seconds wait before retrying a request after a
   * nonce timeout.
   */
  public static final String NONCE_TIMEOUT_RETRY_BASE_TIME_KEY = "org.opencastproject.security.digest.nonce.base.time";

  /**
   * The configuration property specifying the maximum for a random amount of time in seconds above the base time to
   * wait.
   */
  public static final String NONCE_TIMEOUT_RETRY_MAXIMUM_VARIABLE_TIME_KEY =
    "org.opencastproject.security.digest.nonce.variable.time";

  /** The default time until a connection attempt fails */
  public static final int DEFAULT_CONNECTION_TIMEOUT = 60 * 1000;

  /** The default time between packets that causes a connection to fail */
  public static final int DEFAULT_SOCKET_TIMEOUT = 300 * 1000;

  /** The default number of times to attempt a request after it has failed due to a nonce expiring. */
  public static final int DEFAULT_NONCE_TIMEOUT_RETRIES = 12;

  /** The number of milliseconds in a single second. */
  private static final int MILLISECONDS_IN_SECONDS = 1000;

  /** The default amount of time to wait after a nonce timeout. */
  public static final int DEFAULT_RETRY_BASE_TIME = 300;

  /** Default maximum amount of time in a random range between 0 and this value to add to the base time. */
  public static final int DEFAULT_RETRY_MAXIMUM_VARIABLE_TIME = 300;

  /**
   * The default time before a piece of signed content expires. 1 Minute. These are internal calls to another server, if
   * we can't make the request in under a minute something has gone horribly wrong.
   */
  protected static final long DEFAULT_URL_SIGNING_EXPIRES_DURATION = 60;

  /** The configured username to send as part of the digest authenticated request */
  protected String user = null;

  /** The configured password to send as part of the digest authenticated request */
  protected String pass = null;

  /** The number of times to retry a request after a nonce timeout. */
  private int nonceTimeoutRetries = DEFAULT_NONCE_TIMEOUT_RETRIES;

  /** The map of open responses to their http clients, which need to be closed after we are finished with the response */
  protected Map responseMap = new ConcurrentHashMap();

  /** Used to add a random amount of time up to retryMaximumVariableTime to retry a request after a nonce timeout. */
  private Random generator = new Random();

  /** Used to create HttpClients that are used to make http requests. */
  private HttpClientFactory httpClientFactory = null;

  /** The amount of time in seconds to wait until trying the request again. */
  private int retryBaseDelay = 300;

  /** The maximum amount of time in seconds to wait in addition to the RETRY_BASE_DELAY. */
  private int retryMaximumVariableTime = 300;

  /** The duration a signed url will remain valid for. */
  private long signedUrlExpiresDuration = DEFAULT_URL_SIGNING_EXPIRES_DURATION;

  /** The service registry */
  private ServiceRegistry serviceRegistry = null;

  /** The security service */
  protected SecurityService securityService = null;

  /** The url signing service */
  protected UrlSigningService urlSigningService = null;

  public void activate(ComponentContext cc) {
    logger.debug("activate");
    user = cc.getBundleContext().getProperty(DIGEST_AUTH_USER_KEY);
    pass = cc.getBundleContext().getProperty(DIGEST_AUTH_PASS_KEY);
    if (user == null || pass == null)
      throw new IllegalStateException("trusted communication is not properly configured");

    getRetryNumber(cc);
    getRetryBaseTime(cc);
    getRetryMaximumVariableTime(cc);

    // register with jmx
    try {
      MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
      ObjectName name;
      name = new ObjectName("org.opencastproject.security.api.TrustedHttpClient:type=HttpConnections");
      Object mbean = this;
      if (!mbs.isRegistered(name)) {
        mbs.registerMBean(mbean, name);
      }
    } catch (Exception e) {
      logger.warn("Unable to register {} as an mbean: {}", this, e);
    }

    Opt expiration = getOptContextProperty(cc, INTERNAL_URL_SIGNING_DURATION_KEY).toOpt().map(
            com.entwinemedia.fn.fns.Strings.toLongF);
    if (expiration.isSome()) {
      signedUrlExpiresDuration = expiration.get();
      logger.debug("The property {} has been configured to expire signed URLs in {}.",
              INTERNAL_URL_SIGNING_DURATION_KEY, Log.getHumanReadableTimeString(signedUrlExpiresDuration));
    } else {
      signedUrlExpiresDuration = DEFAULT_URL_SIGNING_EXPIRES_DURATION;
      logger.debug(
              "The property {} has not been configured, so the default is being used to expire signed URLs in {}.",
              INTERNAL_URL_SIGNING_DURATION_KEY, Log.getHumanReadableTimeString(signedUrlExpiresDuration));
    }
  }

  /**
   * Sets the service registry.
   *
   * @param serviceRegistry
   *         the serviceRegistry to set
   */
  public void setServiceRegistry(ServiceRegistry serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
  }

  /**
   * Sets the security service.
   *
   * @param securityService
   *         the security service
   */
  public void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  /**
   * Sets the url signing service.
   *
   * @param urlSigningService
   *        The signing service to sign urls with.
   */
  public void setUrlSigningService(UrlSigningService urlSigningService) {
    this.urlSigningService = urlSigningService;
  }

  /**
   * Extracts the number of times to retry a request after a nonce timeout.
   *
   * @param cc
   *         The ComponentContent to extract this property from.
   */
  private void getRetryNumber(ComponentContext cc) {
    nonceTimeoutRetries = getIntFromComponentContext(cc, NONCE_TIMEOUT_RETRY_KEY, DEFAULT_NONCE_TIMEOUT_RETRIES);
  }

  /**
   * Extracts the minimum amount of time in seconds to wait if there is a nonce timeout before retrying.
   *
   * @param cc
   *         The ComponentContent to extract this property from.
   */
  private void getRetryBaseTime(ComponentContext cc) {
    retryBaseDelay = getIntFromComponentContext(cc, NONCE_TIMEOUT_RETRY_BASE_TIME_KEY, DEFAULT_RETRY_BASE_TIME);
  }

  /**
   * Extracts the maximum amount of time in seconds that is added to the base time after a nonce timeout.
   *
   * @param cc
   *         The ComponentContent to extract this property from.
   */
  private void getRetryMaximumVariableTime(ComponentContext cc) {
    retryMaximumVariableTime = getIntFromComponentContext(cc, NONCE_TIMEOUT_RETRY_MAXIMUM_VARIABLE_TIME_KEY,
                                                          DEFAULT_RETRY_MAXIMUM_VARIABLE_TIME);
  }

  /**
   * Gets a property from the ComponentContext that is the base type int.
   *
   * @param cc
   *         The ComponentContext to get the property from.
   * @param key
   *         The key to search the properties for to get the value back.
   * @param defaultValue
   *         The default value to set if the property is malformed or non-existant.
   * @return The int property either as the value from the properties collection or the default value.
   */
  private int getIntFromComponentContext(ComponentContext cc, String key, int defaultValue) {
    int result;
    try {
      String stringValue = cc.getBundleContext().getProperty(key);
      result = Integer.parseInt(StringUtils.trimToNull(stringValue));
    } catch (Exception e) {
      if (cc != null && cc.getBundleContext() != null && cc.getBundleContext().getProperty(key) != null) {
        logger.info("Unable to get property with key " + key + " with value " + cc.getBundleContext().getProperty(key)
                            + " so using default of " + defaultValue + " because of " + e.getMessage());
      } else {
        logger.info("Unable to get property with key " + key + " so using default of " + defaultValue + " because of "
                            + e.getMessage());
      }
      result = defaultValue;
    }

    return result;
  }

  public void deactivate() {
    logger.debug("deactivate");
  }

  public void setHttpClientFactory(HttpClientFactory httpClientFactory) {
    this.httpClientFactory = httpClientFactory;
  }

  public TrustedHttpClientImpl() {
  }

  public TrustedHttpClientImpl(String user, String pass) {
    this.user = user;
    this.pass = pass;
  }

  /** Creates a new HttpClient to use to make requests. */
  public HttpClient makeHttpClient(int connectionTimeout, int socketTimeout) throws TrustedHttpClientException {
    if (httpClientFactory == null) {
      throw new TrustedHttpClientException(new NullPointerException(
              "There is no DefaultHttpClientFactory service available so we cannot make a request"));
    }
    HttpClient httpClient = httpClientFactory.makeHttpClient();
    httpClient.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout);
    httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, socketTimeout);
    return httpClient;
  }

  @Override
  public  Function, Either> run(final HttpUriRequest httpUriRequest) {
    return StandAloneTrustedHttpClientImpl.run(this, httpUriRequest);
  }

  @Override public  RequestRunner runner(HttpUriRequest req) {
    return StandAloneTrustedHttpClientImpl.runner(this, req);
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.security.api.TrustedHttpClient#execute(org.apache.http.client.methods.HttpUriRequest)
   */
  @Override
  public HttpResponse execute(HttpUriRequest httpUriRequest) throws TrustedHttpClientException {
    return execute(httpUriRequest, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
  }

  @Override
  public HttpResponse execute(HttpUriRequest httpUriRequest, int connectionTimeout, int socketTimeout)
          throws TrustedHttpClientException {
    final HttpClient httpClient = makeHttpClient(connectionTimeout, socketTimeout);
    // Add the request header to elicit a digest auth response
    httpUriRequest.setHeader(REQUESTED_AUTH_HEADER, DIGEST_AUTH);
    httpUriRequest.setHeader(SecurityConstants.AUTHORIZATION_HEADER, "true");

    if (serviceRegistry != null && serviceRegistry.getCurrentJob() != null) {
      httpUriRequest.setHeader(CURRENT_JOB_HEADER, Long.toString(serviceRegistry.getCurrentJob().getId()));
    }

    // If a security service has been set, use it to pass the current security context on
    logger.debug("Adding security context to request");
    final Organization organization = securityService.getOrganization();
    if (organization != null) {
      httpUriRequest.setHeader(SecurityConstants.ORGANIZATION_HEADER, organization.getId());
      final User currentUser = securityService.getUser();
      if (currentUser != null) {
        httpUriRequest.setHeader(SecurityConstants.USER_HEADER, currentUser.getUsername());
      }
    }

    if ("GET".equalsIgnoreCase(httpUriRequest.getMethod()) || "HEAD".equalsIgnoreCase(httpUriRequest.getMethod())) {
      // Set the user/pass
      final UsernamePasswordCredentials creds = new UsernamePasswordCredentials(user, pass);
      AuthScope authScope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM, AuthSchemes.DIGEST);
      httpClient.getCredentialsProvider().setCredentials(authScope, creds);
      // Run the request (the http client handles the multiple back-and-forth requests)
      try {
        Opt optSignedHttpUriRequest = getSignedUrl(httpUriRequest);
        HttpResponse response;
        if (optSignedHttpUriRequest.isSome()) {
          logger.debug("Adding url signing to request {} so that it is {}", httpUriRequest.getURI().toString(),
                  optSignedHttpUriRequest.get().getURI().toString());
          response = new HttpResponseWrapper(httpClient.execute(optSignedHttpUriRequest.get()));
        } else {
          logger.debug("Not adding url signing to request {}", httpUriRequest.getURI().toString());
          response = new HttpResponseWrapper(httpClient.execute(httpUriRequest));
        }
        responseMap.put(response, httpClient);
        return response;
      } catch (IOException e) {
        // close the http connection(s)
        httpClient.getConnectionManager().shutdown();
        throw new TrustedHttpClientException(e);
      }
    } else {
      // HttpClient doesn't handle the request dynamics for other verbs (especially when sending a streamed multipart
      // request), so we need to handle the details of the digest auth back-and-forth manually
      manuallyHandleDigestAuthentication(httpUriRequest, httpClient);
      HttpResponse response = null;
      try {
        response = new HttpResponseWrapper(httpClient.execute(httpUriRequest));
        if (nonceTimeoutRetries > 0 && hadNonceTimeoutResponse(response)) {
          httpClient.getConnectionManager().shutdown();
          response = retryAuthAndRequestAfterNonceTimeout(httpUriRequest, response);
        }
        responseMap.put(response, httpClient);
        return response;
      } catch (Exception e) {
        // if we have a response, remove it from the map
        if (response != null) {
          responseMap.remove(response);
        }
        // close the http connection(s)
        httpClient.getConnectionManager().shutdown();
        throw new TrustedHttpClientException(e);
      }
    }
  }

  /**
   * If the request is a GET, sign the URL and return a new {@link HttpUriRequest} that is signed.
   *
   * @param httpUriRequest
   *          The possible URI to sign.
   * @return HttpUriRequest if the request is a GET and is configured to be signed.
   * @throws TrustedHttpClientException
   *           Thrown if there is a problem signing the URL.
   */
  protected Opt getSignedUrl(HttpUriRequest httpUriRequest) throws TrustedHttpClientException {
    if (("GET".equalsIgnoreCase(httpUriRequest.getMethod()) || "HEAD".equalsIgnoreCase(httpUriRequest.getMethod()))
            && ResourceRequestUtil.isNotSigned(httpUriRequest.getURI())
            && urlSigningService.accepts(httpUriRequest.getURI().toString())) {
      logger.trace("Signing request with method: {} and URI: {}", httpUriRequest.getMethod(), httpUriRequest.getURI()
              .toString());
      try {
        String signedUrl = urlSigningService.sign(httpUriRequest.getURI().toString(), signedUrlExpiresDuration, null,
                null);
        HttpRequestBase signedRequest;
        if ("GET".equalsIgnoreCase(httpUriRequest.getMethod())) {
          signedRequest = new HttpGet(signedUrl);
        } else {
          signedRequest = new HttpHead(signedUrl);
        }
        signedRequest.setProtocolVersion(httpUriRequest.getProtocolVersion());
        for (Header header : httpUriRequest.getAllHeaders()) {
          signedRequest.addHeader(header);
        }
        return Opt.some((HttpUriRequest) signedRequest);
      } catch (UrlSigningException e) {
        throw new TrustedHttpClientException(e);
      }
    } else {
      logger.trace("Not signing request with method: {} and URI: {}", httpUriRequest.getMethod(), httpUriRequest
              .getURI().toString());
      return Opt.none();
    }
  }

  /**
   * Retries a request if the nonce timed out during the request.
   *
   * @param httpUriRequest
   *         The request to be made that isn't a GET, those are handled automatically.
   * @param response
   *         The response with the bad nonce timeout in it.
   * @return A new response for the request if it was successful without the nonce timing out again or just the same
   * response it got if it ran out of attempts.
   * @throws TrustedHttpClientException
   * @throws IOException
   * @throws ClientProtocolException
   */
  private HttpResponse retryAuthAndRequestAfterNonceTimeout(HttpUriRequest httpUriRequest, HttpResponse response)
          throws TrustedHttpClientException, IOException, ClientProtocolException {
    // Get rid of old security headers with the old nonce.
    httpUriRequest.removeHeaders(AUTHORIZATION_HEADER_NAME);

    for (int i = 0; i < nonceTimeoutRetries; i++) {
      HttpClient httpClient = makeHttpClient(DEFAULT_CONNECTION_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
      int variableDelay = 0;
      // Make sure that we have a variable delay greater than 0.
      if (retryMaximumVariableTime > 0) {
        variableDelay = generator.nextInt(retryMaximumVariableTime * MILLISECONDS_IN_SECONDS);
      }

      long totalDelay = (retryBaseDelay * MILLISECONDS_IN_SECONDS + variableDelay);
      if (totalDelay > 0) {
        logger.info("Sleeping " + totalDelay + "ms before trying request " + httpUriRequest.getURI()
                            + " again due to a " + response.getStatusLine());
        try {
          Thread.sleep(totalDelay);
        } catch (InterruptedException e) {
          logger.error("Suffered InteruptedException while trying to sleep until next retry.", e);
        }
      }
      manuallyHandleDigestAuthentication(httpUriRequest, httpClient);
      response = new HttpResponseWrapper(httpClient.execute(httpUriRequest));
      if (!hadNonceTimeoutResponse(response)) {
        responseMap.put(response, httpClient);
        break;
      }
      httpClient.getConnectionManager().shutdown();
    }
    return response;
  }

  /**
   * Determines if the nonce has timed out before a request could be performed.
   *
   * @param response
   *         The response to test to see if it has timed out.
   * @return true if it has time out, false if it hasn't
   */
  private boolean hadNonceTimeoutResponse(HttpResponse response) {
    return (401 == response.getStatusLine().getStatusCode())
            && ("Nonce has expired/timed out".equals(response.getStatusLine().getReasonPhrase()));
  }

  /**
   * Handles the necessary handshake for digest authenticaion in the case where it isn't a GET operation.
   *
   * @param httpUriRequest
   *         The request location to get the digest authentication for.
   * @param httpClient
   *         The client to send the request through.
   * @throws TrustedHttpClientException
   *         Thrown if the client cannot be shutdown.
   */
  private void manuallyHandleDigestAuthentication(HttpUriRequest httpUriRequest, HttpClient httpClient)
          throws TrustedHttpClientException {
    HttpRequestBase digestRequest;
    try {
      digestRequest = (HttpRequestBase) httpUriRequest.getClass().newInstance();
    } catch (Exception e) {
      throw new IllegalStateException("Can not create a new " + httpUriRequest.getClass().getName());
    }
    digestRequest.setURI(httpUriRequest.getURI());
    digestRequest.setHeader(REQUESTED_AUTH_HEADER, DIGEST_AUTH);
    String[] realmAndNonce = getRealmAndNonce(digestRequest);

    if (realmAndNonce != null) {
      // Set the user/pass
      UsernamePasswordCredentials creds = new UsernamePasswordCredentials(user, pass);

      // Set up the digest authentication with the required values
      DigestScheme digestAuth = new DigestScheme();
      digestAuth.overrideParamter("realm", realmAndNonce[0]);
      digestAuth.overrideParamter("nonce", realmAndNonce[1]);

      // Add the authentication header
      try {
        httpUriRequest.setHeader(digestAuth.authenticate(creds, httpUriRequest));
      } catch (Exception e) {
        // close the http connection(s)
        httpClient.getConnectionManager().shutdown();
        throw new TrustedHttpClientException(e);
      }
    }
  }

  @Override
  public  T execute(HttpUriRequest httpUriRequest, ResponseHandler responseHandler, int connectionTimeout,
                       int socketTimeout) throws TrustedHttpClientException {
    try {
      return responseHandler.handleResponse(execute(httpUriRequest, connectionTimeout, socketTimeout));
    } catch (IOException e) {
      throw new TrustedHttpClientException(e);
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.security.api.TrustedHttpClient#close(org.apache.http.HttpResponse)
   */
  @Override
  public void close(HttpResponse response) {
    if (response != null) {
      HttpClient httpClient = responseMap.remove(response);
      if (httpClient != null) {
        httpClient.getConnectionManager().shutdown();
      }
    } else {
      logger.debug("Can not close a null response");
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.security.api.TrustedHttpClient#execute(org.apache.http.client.methods.HttpUriRequest,
   * org.apache.http.client.ResponseHandler)
   */
  @Override
  public  T execute(HttpUriRequest httpUriRequest, ResponseHandler responseHandler)
          throws TrustedHttpClientException {
    return execute(httpUriRequest, responseHandler, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
  }

  /**
   * Perform a request, and extract the realm and nonce values
   *
   * @param request
   *         The request to execute in order to obtain the realm and nonce
   * @return A String[] containing the {realm, nonce}
   */
  protected String[] getRealmAndNonce(HttpRequestBase request) throws TrustedHttpClientException {
    HttpClient httpClient = makeHttpClient(DEFAULT_CONNECTION_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
    HttpResponse response;
    try {
      response = new HttpResponseWrapper(httpClient.execute(request));
    } catch (IOException e) {
      httpClient.getConnectionManager().shutdown();
      throw new TrustedHttpClientException(e);
    }
    Header[] headers = response.getHeaders("WWW-Authenticate");
    if (headers == null || headers.length == 0) {
      logger.warn("URI {} does not support digest authentication", request.getURI());
      httpClient.getConnectionManager().shutdown();
      return null;
    }
    Header authRequiredResponseHeader = headers[0];
    String nonce = null;
    String realm = null;
    for (HeaderElement element : authRequiredResponseHeader.getElements()) {
      if ("nonce".equals(element.getName())) {
        nonce = element.getValue();
      } else if ("Digest realm".equals(element.getName())) {
        realm = element.getValue();
      }
    }
    httpClient.getConnectionManager().shutdown();
    return new String[]{realm, nonce};
  }

  @Override
  public int getOpenConnections() {
    return responseMap.size();
  }

  /**
   * @return Returns the number of times the TrustedHttpClient will retry a request if nonce timeouts are occuring.
   */
  public int getNonceTimeoutRetries() {
    return nonceTimeoutRetries;
  }

  /**
   * @return The minimum amount of time to wait in seconds after a nonce timeout before retrying.
   */
  public int getRetryBaseDelay() {
    return retryBaseDelay;
  }

  /**
   * @return The maximum amount of time to wait in seconds after a nonce timeout in addition to the base delay.
   */
  public int getRetryMaximumVariableTime() {
    return retryMaximumVariableTime;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy