Package org.apache.commons.httpclient

Source Code of org.apache.commons.httpclient.HttpMethodDirector

/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.34 2005/01/14 19:40:39 olegk Exp $
* $Revision: 486658 $
* $Date: 2006-12-13 19:35:50 +0530 (Wed, 13 Dec 2006) $
*
* ====================================================================
*
*  Licensed to the Apache Software Foundation (ASF) under one or more
*  contributor license agreements.  See the NOTICE file distributed with
*  this work for additional information regarding copyright ownership.
*  The ASF 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation.  For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/

package org.apache.commons.httpclient;

import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.httpclient.auth.AuthChallengeException;
import org.apache.commons.httpclient.auth.AuthChallengeParser;
import org.apache.commons.httpclient.auth.AuthChallengeProcessor;
import org.apache.commons.httpclient.auth.AuthScheme;
import org.apache.commons.httpclient.auth.AuthState;
import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.commons.httpclient.auth.ClaimsAuthScheme;
import org.apache.commons.httpclient.auth.CredentialsProvider;
import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.auth.MalformedChallengeException;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HostParams;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.params.HttpParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Handles the process of executing a method including authentication, redirection and retries.
*
* @since 3.0
*/
class HttpMethodDirector {

    /** The www authenticate challange header. */
    public static final String WWW_AUTH_CHALLENGE = "WWW-Authenticate";

    /** The www authenticate response header. */
    public static final String WWW_AUTH_RESP = "Authorization";

    /** The proxy authenticate challange header. */
    public static final String PROXY_AUTH_CHALLENGE = "Proxy-Authenticate";

    /** The proxy authenticate response header. */
    public static final String PROXY_AUTH_RESP = "Proxy-Authorization";

    private static final Log LOG = LogFactory.getLog(HttpMethodDirector.class);

    private ConnectMethod connectMethod;
   
    private HttpState state;
 
    private HostConfiguration hostConfiguration;
   
    private HttpConnectionManager connectionManager;
   
    private HttpClientParams params;
   
    private HttpConnection conn;
   
    /** A flag to indicate if the connection should be released after the method is executed. */
    private boolean releaseConnection = false;

    /** Authentication processor */
    private AuthChallengeProcessor authProcessor = null;

    private Set redirectLocations = null;
   
    public HttpMethodDirector(
        final HttpConnectionManager connectionManager,
        final HostConfiguration hostConfiguration,
        final HttpClientParams params,
        final HttpState state
    ) {
        super();
        this.connectionManager = connectionManager;
        this.hostConfiguration = hostConfiguration;
        this.params = params;
        this.state = state;
        this.authProcessor = new AuthChallengeProcessor(this.params);
    }
   
 
    /**
     * Executes the method associated with this method director.
     *
     * @throws IOException
     * @throws HttpException
     */
    public void executeMethod(final HttpMethod method) throws IOException, HttpException {
        if (method == null) {
            throw new IllegalArgumentException("Method may not be null");
        }
        // Link all parameter collections to form the hierarchy:
        // Global -> HttpClient -> HostConfiguration -> HttpMethod
        this.hostConfiguration.getParams().setDefaults(this.params);
        method.getParams().setDefaults(this.hostConfiguration.getParams());
       
        // Generate default request headers
        Collection defaults = (Collection)this.hostConfiguration.getParams().
      getParameter(HostParams.DEFAULT_HEADERS);
        if (defaults != null) {
          Iterator i = defaults.iterator();
          while (i.hasNext()) {
            method.addRequestHeader((Header)i.next());
          }
        }
       
        try {
            int maxRedirects = this.params.getIntParameter(HttpClientParams.MAX_REDIRECTS, 100);

            for (int redirectCount = 0;;) {

                // make sure the connection we have is appropriate
                if (this.conn != null && !hostConfiguration.hostEquals(this.conn)) {
                    this.conn.setLocked(false);
                    this.conn.releaseConnection();
                    this.conn = null;
                }
       
                // get a connection, if we need one
                if (this.conn == null) {
                    this.conn = connectionManager.getConnectionWithTimeout(
                        hostConfiguration,
                        this.params.getConnectionManagerTimeout()
                    );
                    this.conn.setLocked(true);
                    if (this.params.isAuthenticationPreemptive()
                     || this.state.isAuthenticationPreemptive())
                    {
                        LOG.debug("Preemptively sending default basic credentials");
                        method.getHostAuthState().setPreemptive();
                        method.getHostAuthState().setAuthAttempted(true);
                        if (this.conn.isProxied() && !this.conn.isSecure()) {
                            method.getProxyAuthState().setPreemptive();
                            method.getProxyAuthState().setAuthAttempted(true);
                        }
                    }
                }
                authenticate(method);
                executeWithRetry(method);
                if (this.connectMethod != null) {
                    fakeResponse(method);
                    break;
                }
               
                boolean retry = false;

                if (isRedirectNeeded(method)) {
                    if (processRedirectResponse(method)) {
                        retry = true;
                        ++redirectCount;
                        if (redirectCount >= maxRedirects) {
                            LOG.error("Narrowly avoided an infinite loop in execute");
                            throw new RedirectException("Maximum redirects ("
                                + maxRedirects + ") exceeded");
                        }
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Execute redirect " + redirectCount + " of " + maxRedirects);
                        }
                    }
                }
                if (isAuthenticationNeeded(method)) {
                    if (processAuthenticationResponse(method)) {
                        LOG.debug("Retry authentication");
                        retry = true;
                    }
                }
                if (!retry) {
                    break;
                }
                // retry - close previous stream.  Caution - this causes
                // responseBodyConsumed to be called, which may also close the
                // connection.
                if (method.getResponseBodyAsStream() != null) {
                    method.getResponseBodyAsStream().close();
                }

            } //end of retry loop
        } finally {
            if (this.conn != null) {
                this.conn.setLocked(false);
            }
            // If the response has been fully processed, return the connection
            // to the pool.  Use this flag, rather than other tests (like
            // responseStream == null), as subclasses, might reset the stream,
            // for example, reading the entire response into a file and then
            // setting the file as the stream.
            if (
                (releaseConnection || method.getResponseBodyAsStream() == null)
                && this.conn != null
            ) {
                this.conn.releaseConnection();
            }
        }

    }

   
    private void authenticate(final HttpMethod method) {
        try {
            if (this.conn.isProxied() && !this.conn.isSecure()) {
                authenticateProxy(method);
            }
            authenticateHost(method);
        } catch (AuthenticationException e) {
            LOG.error(e.getMessage(), e);
        }
    }


    private boolean cleanAuthHeaders(final HttpMethod method, final String name) {
        Header[] authheaders = method.getRequestHeaders(name);
        boolean clean = true;
        for (int i = 0; i < authheaders.length; i++) {
            Header authheader = authheaders[i];
            if (authheader.isAutogenerated()) {
                method.removeRequestHeader(authheader);
            } else {
                clean = false;
            }
        }
        return clean;
    }
   

    private void authenticateHost(final HttpMethod method) throws AuthenticationException {
        // Clean up existing authentication headers
        if (!cleanAuthHeaders(method, WWW_AUTH_RESP)) {
            // User defined authentication header(s) present
            return;
        }
        AuthState authstate = method.getHostAuthState();
       
        AuthScheme authscheme = authstate.getAuthScheme();
       
        if (authscheme == null) {
            return;
        }
       
        if (authscheme instanceof ClaimsAuthScheme) {
          authscheme = ((ClaimsAuthScheme)authscheme).innerAuthScheme;
        }
       
        if (authscheme == null) {
            return;
        }
       
        if (authstate.isAuthRequested() || !authscheme.isConnectionBased()) {
            String host = method.getParams().getVirtualHost();
            if (host == null) {
                host = conn.getHost();
            }
            int port = conn.getPort();
            AuthScope authscope = new AuthScope(
                host, port,
                authscheme.getRealm(),
                authscheme.getSchemeName())
            if (LOG.isDebugEnabled()) {
                LOG.debug("Authenticating with " + authscope);
            }
            Credentials credentials = this.state.getCredentials(authscope);
            if (credentials != null) {
                String authstring = authscheme.authenticate(credentials, method);
                if (authstring != null) {
                    method.addRequestHeader(new Header(WWW_AUTH_RESP, authstring, true));
                }
            } else {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("Required credentials not available for " + authscope);
                    if (method.getHostAuthState().isPreemptive()) {
                        LOG.warn("Preemptive authentication requested but no default " +
                            "credentials available");
                    }
                }
            }
        }
    }


    private void authenticateProxy(final HttpMethod method) throws AuthenticationException {
        // Clean up existing authentication headers
        if (!cleanAuthHeaders(method, PROXY_AUTH_RESP)) {
            // User defined authentication header(s) present
            return;
        }
        AuthState authstate = method.getProxyAuthState();
        AuthScheme authscheme = authstate.getAuthScheme();
        if (authscheme == null) {
            return;
        }
        if (authstate.isAuthRequested() || !authscheme.isConnectionBased()) {
            AuthScope authscope = new AuthScope(
                conn.getProxyHost(), conn.getProxyPort(),
                authscheme.getRealm(),
                authscheme.getSchemeName())
            if (LOG.isDebugEnabled()) {
                LOG.debug("Authenticating with " + authscope);
            }
            Credentials credentials = this.state.getProxyCredentials(authscope);
            if (credentials != null) {
                String authstring = authscheme.authenticate(credentials, method);
                if (authstring != null) {
                    method.addRequestHeader(new Header(PROXY_AUTH_RESP, authstring, true));
                }
            } else {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("Required proxy credentials not available for " + authscope);
                    if (method.getProxyAuthState().isPreemptive()) {
                        LOG.warn("Preemptive authentication requested but no default " +
                            "proxy credentials available");
                    }
                }
            }
        }
    }
   
   
    /**
     * Applies connection parameters specified for a given method
     *
     * @param method HTTP method
     *
     * @throws IOException if an I/O occurs setting connection parameters
     */
    private void applyConnectionParams(final HttpMethod method) throws IOException {
        int timeout = 0;
        // see if a timeout is given for this method
        Object param = method.getParams().getParameter(HttpMethodParams.SO_TIMEOUT);
        if (param == null) {
            // if not, use the default value
            param = this.conn.getParams().getParameter(HttpConnectionParams.SO_TIMEOUT);
        }
        if (param != null) {
            timeout = ((Integer)param).intValue();
        }
        this.conn.setSocketTimeout(timeout);                   
    }
   
    /**
     * Executes a method with the current hostConfiguration.
     *
     * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
     * can be recovered from.
     * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions
     * cannot be recovered from.
     */
    private void executeWithRetry(final HttpMethod method)
        throws IOException, HttpException {
       
        /** How many times did this transparently handle a recoverable exception? */
        int execCount = 0;
        // loop until the method is successfully processed, the retryHandler
        // returns false or a non-recoverable exception is thrown
        try {
            while (true) {
                execCount++;
                try {

                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Attempt number " + execCount + " to process request");
                    }
                    if (this.conn.getParams().isStaleCheckingEnabled()) {
                        this.conn.closeIfStale();
                    }
                    if (!this.conn.isOpen()) {
                        // this connection must be opened before it can be used
                        // This has nothing to do with opening a secure tunnel
                        this.conn.open();
                        if (this.conn.isProxied() && this.conn.isSecure()
                        && !(method instanceof ConnectMethod)) {
                            // we need to create a secure tunnel before we can execute the real method
                            if (!executeConnect()) {
                                // abort, the connect method failed
                                return;
                            }
                        }
                    }
                    applyConnectionParams(method);                   
                    method.execute(state, this.conn);
                    break;
                } catch (HttpException e) {
                    // filter out protocol exceptions which cannot be recovered from
                    throw e;
                } catch (IOException e) {
                    LOG.debug("Closing the connection.");
                    this.conn.close();
                    // test if this method should be retried
                    // ========================================
                    // this code is provided for backward compatibility with 2.0
                    // will be removed in the next major release
                    if (method instanceof HttpMethodBase) {
                        MethodRetryHandler handler =
                            ((HttpMethodBase)method).getMethodRetryHandler();
                        if (handler != null) {
                            if (!handler.retryMethod(
                                    method,
                                    this.conn,
                                    new HttpRecoverableException(e.getMessage()),
                                    execCount,
                                    method.isRequestSent())) {
                                LOG.debug("Method retry handler returned false. "
                                        + "Automatic recovery will not be attempted");
                                throw e;
                            }
                        }
                    }
                    // ========================================
                    HttpMethodRetryHandler handler =
                        (HttpMethodRetryHandler)method.getParams().getParameter(
                                HttpMethodParams.RETRY_HANDLER);
                    if (handler == null) {
                        handler = new DefaultHttpMethodRetryHandler();
                    }
                    if (!handler.retryMethod(method, e, execCount)) {
                        LOG.debug("Method retry handler returned false. "
                                + "Automatic recovery will not be attempted");
                        throw e;
                    }
                    if (LOG.isInfoEnabled()) {
                        LOG.info("I/O exception ("+ e.getClass().getName() +") caught when processing request: "
                                + e.getMessage());
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(e.getMessage(), e);
                    }
                    LOG.info("Retrying request");
                }
            }
        } catch (IOException e) {
            if (this.conn.isOpen()) {
                LOG.debug("Closing the connection.");
                this.conn.close();
            }
            releaseConnection = true;
            throw e;
        } catch (RuntimeException e) {
            if (this.conn.isOpen()) {
                LOG.debug("Closing the connection.");
                this.conn.close();
            }
            releaseConnection = true;
            throw e;
        }
    }
   
    /**
     * Executes a ConnectMethod to establish a tunneled connection.
     *
     * @return <code>true</code> if the connect was successful
     *
     * @throws IOException
     * @throws HttpException
     */
    private boolean executeConnect()
        throws IOException, HttpException {

        this.connectMethod = new ConnectMethod(this.hostConfiguration);
        this.connectMethod.getParams().setDefaults(this.hostConfiguration.getParams());
       
        int code;
        for (;;) {
            if (!this.conn.isOpen()) {
                this.conn.open();
            }
            if (this.params.isAuthenticationPreemptive()
                    || this.state.isAuthenticationPreemptive()) {
                LOG.debug("Preemptively sending default basic credentials");
                this.connectMethod.getProxyAuthState().setPreemptive();
                this.connectMethod.getProxyAuthState().setAuthAttempted(true);
            }
            try {
                authenticateProxy(this.connectMethod);
            } catch (AuthenticationException e) {
                LOG.error(e.getMessage(), e);
            }
            applyConnectionParams(this.connectMethod);                   
            this.connectMethod.execute(state, this.conn);
            code = this.connectMethod.getStatusCode();
            boolean retry = false;
            AuthState authstate = this.connectMethod.getProxyAuthState();
            authstate.setAuthRequested(code == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
            if (authstate.isAuthRequested()) {
                if (processAuthenticationResponse(this.connectMethod)) {
                    retry = true;
                }
            }
            if (!retry) {
                break;
            }
            if (this.connectMethod.getResponseBodyAsStream() != null) {
                this.connectMethod.getResponseBodyAsStream().close();
            }
        }
        if ((code >= 200) && (code < 300)) {
            this.conn.tunnelCreated();
            // Drop the connect method, as it is no longer needed
            this.connectMethod = null;
            return true;
        } else {
            this.conn.close();
            return false;
        }
    }

    /**
     * Fake response
     * @param method
     * @return
     */
   
    private void fakeResponse(final HttpMethod method)
        throws IOException, HttpException {
        // What is to follow is an ugly hack.
        // I REALLY hate having to resort to such
        // an appalling trick
        // The only feasible solution is to split monolithic
        // HttpMethod into HttpRequest/HttpResponse pair.
        // That would allow to execute CONNECT method
        // behind the scene and return CONNECT HttpResponse
        // object in response to the original request that
        // contains the correct status line, headers &
        // response body.
        LOG.debug("CONNECT failed, fake the response for the original method");
        // Pass the status, headers and response stream to the wrapped
        // method.
        // To ensure that the connection is not released more than once
        // this method is still responsible for releasing the connection.
        // This will happen when the response body is consumed, or when
        // the wrapped method closes the response connection in
        // releaseConnection().
        if (method instanceof HttpMethodBase) {
            ((HttpMethodBase) method).fakeResponse(
                this.connectMethod.getStatusLine(),
                this.connectMethod.getResponseHeaderGroup(),
                this.connectMethod.getResponseBodyAsStream()
            );
            method.getProxyAuthState().setAuthScheme(
                this.connectMethod.getProxyAuthState().getAuthScheme());
            this.connectMethod = null;
        } else {
            releaseConnection = true;
            LOG.warn(
                "Unable to fake response on method as it is not derived from HttpMethodBase.");
        }
    }
   
  /**
   * Process the redirect response.
     *
   * @return <code>true</code> if the redirect was successful
   */
  private boolean processRedirectResponse(final HttpMethod method)
     throws RedirectException {
    //get the location header to find out where to redirect to
    Header locationHeader = method.getResponseHeader("location");
    if (locationHeader == null) {
      // got a redirect response, but no location header
      LOG.error("Received redirect response " + method.getStatusCode()
          + " but no location header");
      return false;
    }
    String location = locationHeader.getValue();
    if (LOG.isDebugEnabled()) {
      LOG.debug("Redirect requested to location '" + location + "'");
    }
       
    //rfc2616 demands the location value be a complete URI
    //Location       = "Location" ":" absoluteURI
    URI redirectUri = null;
    URI currentUri = null;

    try {
      currentUri = new URI(
        this.conn.getProtocol().getScheme(),
        null,
                this.conn.getHost(),
                this.conn.getPort(),
        method.getPath()
      );
     
            try {
              String charset = method.getParams().getUriCharset();
              redirectUri = new URI(location, true, charset);
            } catch(URIException ue) { // added to resolve org.apache.commons.httpclient.InvalidRedirectLocationException exception
          Object encoding = this.conn.getParams().getParameter("http.protocol.element-charset");
          if(encoding != null) {
            redirectUri = new URI(location, false, (String)encoding);
          } else {
            throw new InvalidRedirectLocationException(
                          "Invalid redirect location: " + location, location, ue);
          }
            }

        if (redirectUri.isRelativeURI()) {
        if (this.params.isParameterTrue(HttpClientParams.REJECT_RELATIVE_REDIRECT)) {
          LOG.warn("Relative redirect location '" + location + "' not allowed");
          return false;
        } else {
          //location is incomplete, use current values for defaults
          LOG.debug("Redirect URI is not absolute - parsing as relative");
          redirectUri = new URI(currentUri, redirectUri);
        }
      } else {
                // Reset the default params
                method.getParams().setDefaults(this.params);
            }
            method.setURI(redirectUri);
            hostConfiguration.setHost(redirectUri);
    } catch (URIException ex) {
            throw new InvalidRedirectLocationException(
                    "Invalid redirect location: " + location, location, ex);
    }

        if (this.params.isParameterFalse(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS)) {
            if (this.redirectLocations == null) {
                this.redirectLocations = new HashSet();
            }
            this.redirectLocations.add(currentUri);
            try {
                if(redirectUri.hasQuery()) {
                    redirectUri.setQuery(null);
                }
            } catch (URIException e) {
                // Should never happen
                return false;
            }

            if (this.redirectLocations.contains(redirectUri)) {
                throw new CircularRedirectException("Circular redirect to '" +
                    redirectUri + "'");
            }
        }

    if (LOG.isDebugEnabled()) {
      LOG.debug("Redirecting from '" + currentUri.getEscapedURI()
        + "' to '" + redirectUri.getEscapedURI());
    }
        //And finally invalidate the actual authentication scheme
    if (!(method.getHostAuthState().getAuthScheme() instanceof ClaimsAuthScheme))
            method.getHostAuthState().invalidate();
    return true;
  }

  /**
   * Processes a response that requires authentication
   *
   * @param method the current {@link HttpMethod HTTP method}
   *
   * @return <tt>true</tt> if the authentication challenge can be responsed to,
     *   (that is, at least one of the requested authentication scheme is supported,
     *   and matching credentials have been found), <tt>false</tt> otherwise.
   */
  private boolean processAuthenticationResponse(final HttpMethod method) {
    LOG.trace("enter HttpMethodBase.processAuthenticationResponse("
      + "HttpState, HttpConnection)");
    try {
            switch (method.getStatusCode()) {
                case HttpStatus.SC_UNAUTHORIZED:
                    return processWWWAuthChallenge(method);
                case HttpStatus.SC_FORBIDDEN:
                case HttpStatus.SC_MOVED_TEMPORARILY:
                    return processClaimsAuthChallenge(method);
                case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
                    return processProxyAuthChallenge(method);
                default:
                    return false;
            }
        } catch (Exception e) {
            if (LOG.isErrorEnabled()) {
                LOG.error(e.getMessage(), e);
            }
            return false;
        }
    }

    private static String httpExchange(HttpMethod method) throws IOException {
      StringBuilder sb = new StringBuilder();
      sb.append(
          method.getName()).append(" ").append(method.getURI()).append("\n");
      for (Header h : method.getRequestHeaders()) {
        sb.append(h.getName()).append(": ").append(h.getValue()).append("\n");
      }
      sb.append("\n");

      sb.append(method.getStatusLine()).append("\n");
      for (Header h : method.getResponseHeaders()) {
        sb.append(h.getName()).append(": ").append(h.getValue()).append("\n");
      }
      sb.append(method.getResponseBodyAsString());
      return sb.toString();
    }

    private boolean adfsLogin(String sharepoint, Credentials credentials)
        throws HttpException, IOException {
      if (System.getProperty("adfs.stsendpoint") == null) {
        return false;
      }
      if (!(credentials instanceof NTCredentials)) {
        LOG.error("ADFS login requested without NT Credentials");
        return false;
      }
      NTCredentials creds = (NTCredentials) credentials;
      String stsendpoint = System.getProperty("adfs.stsendpoint");
      String trust = sharepoint + "/_trust";
      String wstrust = String.format("<?xml version='1.0'?><s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:a='http://www.w3.org/2005/08/addressing' xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'><s:Header><a:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand='1'>%s</a:To><o:Security xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' s:mustUnderstand='1'><o:UsernameToken><o:Username>%s</o:Username><o:Password>%s</o:Password></o:UsernameToken></o:Security></s:Header><s:Body><t:RequestSecurityToken xmlns:t='http://schemas.xmlsoap.org/ws/2005/02/trust'><wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'><a:EndpointReference><a:Address>%s</a:Address></a:EndpointReference></wsp:AppliesTo><t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType><t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType><t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType></t:RequestSecurityToken></s:Body></s:Envelope>",
          stsendpoint,
          creds.getDomain() + "\\" + creds.getUserName(),
          creds.getPassword(),
          trust);
          PostMethod post = new PostMethod(stsendpoint);
      post.addRequestHeader(
          "Content-Type", "application/soap+xml; charset=utf-8");
      post.setRequestBody(wstrust);
      hostConfiguration.setHost(post.getURI());
      executeMethod(post);
      if (post.getStatusCode() != HttpStatus.SC_OK) {
        LOG.error("Unable to login into STS\n" + httpExchange(post));
        return false;
      }
      String response = post.getResponseBodyAsString();
      LOG.debug("STS response: " + response);
      int tokenstart = response.indexOf("RequestSecurityTokenResponse");
      while (response.charAt(tokenstart) != '<') {
        tokenstart--;
      }

      int tokenend = response.lastIndexOf("RequestSecurityTokenResponse");
      while (response.charAt(tokenend) != '>') {
        tokenend++;
      }

      response = response.substring(tokenstart, tokenend + 1);
      LOG.debug("RequestSecurityTokenResponse: " + response);
      String saml = sharepoint + "_layouts/Authenticate.aspx?Source=%2F";

      post = new PostMethod(trust);
      post.addParameter("wa", "wsignin1.0");
      post.addParameter("wctx", saml);
      post.addParameter("wresult", response);
      hostConfiguration.setHost(post.getURI());

      executeMethod(post);

      if (post.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) {
        LOG.error("Unable to establish trust\n" + httpExchange(post));
      }
      return true;
    }

    private boolean processClaimsAuthChallenge(HttpMethod method)
            throws MalformedChallengeException, AuthenticationException, IOException
    {
        AuthState authstate = method.getHostAuthState();
        if (authstate.getAuthScheme() == null) {
            authstate.setAuthScheme(new ClaimsAuthScheme());
        }
        ClaimsAuthScheme claims = (ClaimsAuthScheme)authstate.getAuthScheme();

        AuthScope authscope = new AuthScope(
            method.getHostConfiguration().getHost(),
            method.getHostConfiguration().getPort(),
            claims.getRealm(),
            claims.getSchemeName());

        CredentialsProvider cp = (CredentialsProvider)method.getParams().getParameter(
             CredentialsProvider.PROVIDER);
        Credentials creds = (cp == null) ?
            state.getCredentials(AuthScope.ANY) :
            cp.getCredentials(claims, authscope.getHost(), authscope.getPort(), false);
           
        if (adfsLogin(method.getURI().getScheme()
            + "://" + method.getURI().getAuthority(), creds)) {
          claims.setComplete();
          return true;
        }

        this.state.setCredentials(authscope, creds);
        if (method.getStatusCode() == HttpStatus.SC_FORBIDDEN) {
            claims.originalUri = method.getURI();
            method.setRequestHeader(new Header("User-Agent", "Mozilla/4.0"));
            Header xforms = method.getResponseHeader("X-Forms_Based_Auth_Required");
            if (xforms == null) {
                throw new MalformedChallengeException("Status 403 Forbidden received from" +
                    "server, but X-Forms_Based_Auth_Required header missing - not claims " +
                    "based authentication");
            }
            URI xform = new URI(xforms.getValue().split(", ")[0]);
            hostConfiguration.setHost(xform);
            method.setURI(xform);
        } else if (method.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) {
            Header cookie = method.getResponseHeader("Set-Cookie");
            if (cookie != null && cookie.getValue().startsWith("FedAuth")) {
                method.removeRequestHeader("Authorization");
                method.setURI(claims.originalUri);
                hostConfiguration.setHost(claims.originalUri);
                claims.setComplete();
                authstate.setAuthScheme(null);
            }
        }
        return true;
    }

    private boolean processWWWAuthChallenge(final HttpMethod method)
        throws MalformedChallengeException, AuthenticationException 
    {
        AuthState authstate = method.getHostAuthState();
        Map challenges = AuthChallengeParser.parseChallenges(
            method.getResponseHeaders(WWW_AUTH_CHALLENGE));
        if (challenges.isEmpty()) {
            LOG.debug("Authentication challenge(s) not found");
            return false;
        }
        AuthScheme authscheme = null;
        try {
            authscheme = this.authProcessor.processChallenge(authstate, challenges);
            if (authscheme instanceof ClaimsAuthScheme) {
                authscheme = ((ClaimsAuthScheme)authscheme).innerAuthScheme;
            }
        } catch (AuthChallengeException e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(e.getMessage());
            }
        }
        if (authscheme == null) {
            return false;
        }
        String host = method.getParams().getVirtualHost();
        if (host == null) {
            host = conn.getHost();
        }
        int port = conn.getPort();
        AuthScope authscope = new AuthScope(
            host, port,
            authscheme.getRealm(),
            authscheme.getSchemeName());

        if (LOG.isDebugEnabled()) {
            LOG.debug("Authentication scope: " + authscope);
        }
        if (authstate.isAuthAttempted() && authscheme.isComplete()) {
            // Already tried and failed
            Credentials credentials = promptForCredentials(
                authscheme, method.getParams(), authscope);
            if (credentials == null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Failure authenticating with " + authscope);
                }
                return false;
            } else {
                return true;
            }
        } else {
            authstate.setAuthAttempted(true);
            Credentials credentials = this.state.getCredentials(authscope);
            if (credentials == null) {
                credentials = promptForCredentials(
                    authscheme, method.getParams(), authscope);
            }
            if (credentials == null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("No credentials available for " + authscope);
                }
                return false;
            } else {
                return true;
            }
        }
    }

    private boolean processProxyAuthChallenge(final HttpMethod method)
        throws MalformedChallengeException, AuthenticationException
   
        AuthState authstate = method.getProxyAuthState();
        Map proxyChallenges = AuthChallengeParser.parseChallenges(
            method.getResponseHeaders(PROXY_AUTH_CHALLENGE));
        if (proxyChallenges.isEmpty()) {
            LOG.debug("Proxy authentication challenge(s) not found");
            return false;
        }
        AuthScheme authscheme = null;
        try {
            authscheme = this.authProcessor.processChallenge(authstate, proxyChallenges);
        } catch (AuthChallengeException e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(e.getMessage());
            }
        }
        if (authscheme == null) {
            return false;
        }
        AuthScope authscope = new AuthScope(
            conn.getProxyHost(), conn.getProxyPort(),
            authscheme.getRealm(),
            authscheme.getSchemeName())

        if (LOG.isDebugEnabled()) {
            LOG.debug("Proxy authentication scope: " + authscope);
        }
        if (authstate.isAuthAttempted() && authscheme.isComplete()) {
            // Already tried and failed
            Credentials credentials = promptForProxyCredentials(
                authscheme, method.getParams(), authscope);
            if (credentials == null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Failure authenticating with " + authscope);
                }
                return false;
            } else {
                return true;
            }
        } else {
            authstate.setAuthAttempted(true);
            Credentials credentials = this.state.getProxyCredentials(authscope);
            if (credentials == null) {
                credentials = promptForProxyCredentials(
                    authscheme, method.getParams(), authscope);
            }
            if (credentials == null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("No credentials available for " + authscope);
                }
                return false;
            } else {
                return true;
            }
        }
    }

    /**
     * Tests if the {@link HttpMethod method} requires a redirect to another location.
     *
     * @param method HTTP method
     *
     * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
     */
  private boolean isRedirectNeeded(final HttpMethod method) {
    switch (method.getStatusCode()) {
      case HttpStatus.SC_MOVED_TEMPORARILY:
      case HttpStatus.SC_MOVED_PERMANENTLY:
      case HttpStatus.SC_SEE_OTHER:
      case HttpStatus.SC_TEMPORARY_REDIRECT:
        LOG.debug("Redirect required");
        if (method.getHostAuthState() != null &&
            method.getHostAuthState().getAuthScheme() instanceof ClaimsAuthScheme)
            return true;
                if (method.getFollowRedirects()) {
                    return true;
                } else {
                    return false;
                }
      default:
        return false;
    } //end of switch
  }

    /**
     * Tests if the {@link HttpMethod method} requires authentication.
     *
     * @param method HTTP method
     *
     * @return boolean <tt>true</tt> if a retry is needed, <tt>false</tt> otherwise.
     */
    private boolean isAuthenticationNeeded(final HttpMethod method) {
        method.getHostAuthState().setAuthRequested(
            (method.getStatusCode() == HttpStatus.SC_UNAUTHORIZED ||
            method.getStatusCode() == HttpStatus.SC_FORBIDDEN ||
            method.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY &&
            (method.getHostAuthState().getAuthScheme() != null &&
            method.getHostAuthState().getAuthScheme().isComplete() == false)));
       
        method.getProxyAuthState().setAuthRequested(
                method.getStatusCode() == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
        if (method.getHostAuthState().isAuthRequested() ||
            method.getProxyAuthState().isAuthRequested()) {
            LOG.debug("Authorization required");
            if (method.getDoAuthentication()) { //process authentication response
                return true;
            } else { //let the client handle the authenticaiton
                LOG.info("Authentication requested but doAuthentication is "
                        + "disabled");
                return false;
            }
        } else {
            return false;
        }
    }

    private Credentials promptForCredentials(
        final AuthScheme authScheme,
        final HttpParams params,
        final AuthScope authscope)
    {
        LOG.debug("Credentials required");
        Credentials creds = null;
        CredentialsProvider credProvider =
            (CredentialsProvider)params.getParameter(CredentialsProvider.PROVIDER);
        if (credProvider != null) {
            try {
                creds = credProvider.getCredentials(
                    authScheme, authscope.getHost(), authscope.getPort(), false);
            } catch (CredentialsNotAvailableException e) {
                LOG.warn(e.getMessage());
            }
            if (creds != null) {
                this.state.setCredentials(authscope, creds);
                if (LOG.isDebugEnabled()) {
                    LOG.debug(authscope + " new credentials given");
                }
            }
        } else {
            LOG.debug("Credentials provider not available");
        }
        return creds;
    }

    private Credentials promptForProxyCredentials(
        final AuthScheme authScheme,
        final HttpParams params,
        final AuthScope authscope)
    {
        LOG.debug("Proxy credentials required");
        Credentials creds = null;
        CredentialsProvider credProvider =
            (CredentialsProvider)params.getParameter(CredentialsProvider.PROVIDER);
        if (credProvider != null) {
            try {
                creds = credProvider.getCredentials(
                    authScheme, authscope.getHost(), authscope.getPort(), true);
            } catch (CredentialsNotAvailableException e) {
                LOG.warn(e.getMessage());
            }
            if (creds != null) {
                this.state.setProxyCredentials(authscope, creds);
                if (LOG.isDebugEnabled()) {
                    LOG.debug(authscope + " new credentials given");
                }
            }
        } else {
            LOG.debug("Proxy credentials provider not available");
        }
        return creds;
    }

    /**
     * @return
     */
    public HostConfiguration getHostConfiguration() {
        return hostConfiguration;
    }

    /**
     * @return
     */
    public HttpState getState() {
        return state;
    }

    /**
     * @return
     */
    public HttpConnectionManager getConnectionManager() {
        return connectionManager;
    }

    /**
     * @return
     */
    public HttpParams getParams() {
        return this.params;
    }
}
TOP

Related Classes of org.apache.commons.httpclient.HttpMethodDirector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.