/*
* $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java,v 1.7.2.4 2004/06/11 18:55:32 olegk Exp $
* $Revision: 1.7.2.4 $
* $Date: 2004/06/11 18:55:32 $
*
* ====================================================================
*
* Copyright 1999-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* 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/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient.auth;
import java.util.Map;
import java.util.HashMap;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility methods for HTTP authorization and authentication. This class
* provides utility methods for generating responses to HTTP www and proxy
* authentication challenges.
*
* <blockquote>
* A client SHOULD assume that all paths at or deeper than the depth of the
* last symbolic element in the path field of the Request-URI also are within
* the protection space specified by the basic realm value of the current
* challenge. A client MAY preemptively send the corresponding Authorization
* header with requests for resources in that space without receipt of another
* challenge from the server. Similarly, when a client sends a request to a
* proxy, it may reuse a userid and password in the Proxy-Authorization header
* field without receiving another challenge from the proxy server.
* </blockquote>
* </p>
*
* @author <a href="mailto:[email protected]">Remy Maucherat</a>
* @author Rodney Waldhoff
* @author <a href="mailto:[email protected]">Jeff Dever</a>
* @author Ortwin Gl�ck
* @author Sean C. Sullivan
* @author <a href="mailto:[email protected]">Adrian Sutton</a>
* @author <a href="mailto:[email protected]">Mike Bowler</a>
* @author <a href="mailto:[email protected]">Oleg Kalnichevski</a>
*/
public final class HttpAuthenticator {
/** Log object for this class. */
private static final Log LOG = LogFactory.getLog(HttpAuthenticator.class);
/**
* The www authenticate challange header.
*/
public static final String WWW_AUTH = "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 = "Proxy-Authenticate";
/**
* The proxy authenticate response header.
*/
public static final String PROXY_AUTH_RESP = "Proxy-Authorization";
/** Chooses the strongest authentication scheme supported from the
* array of authentication challenges. Currently only <code>NTLM</code>,
* <code>Digest</code>, <code>Basic</code> schemes are recognized.
* The <code>NTLM</code> scheme is considered the strongest and is
* preferred to all others. The <code>Digest</code> scheme is preferred to
* the <code>Basic</code> one which provides no encryption for credentials.
* The <code>Basic</code> scheme is used only if it is the only one
* supported.
*
* @param challenges The array of authentication challenges
*
* @return The strongest authentication scheme supported
*
* @throws MalformedChallengeException is thrown if an authentication
* challenge is malformed
* @throws UnsupportedOperationException when none of challenge types
* available is supported.
*/
public static AuthScheme selectAuthScheme(final Header[] challenges)
throws MalformedChallengeException {
LOG.trace("enter HttpAuthenticator.selectAuthScheme(Header[])");
if (challenges == null) {
throw new IllegalArgumentException("Array of challenges may not be null");
}
if (challenges.length == 0) {
throw new IllegalArgumentException("Array of challenges may not be empty");
}
String challenge = null;
Map challengemap = new HashMap(challenges.length);
for (int i = 0; i < challenges.length; i++) {
challenge = challenges[i].getValue();
String s = AuthChallengeParser.extractScheme(challenge);
challengemap.put(s, challenge);
}
challenge = (String) challengemap.get("ntlm");
if (challenge != null) {
return new NTLMScheme(challenge);
}
challenge = (String) challengemap.get("digest");
if (challenge != null) {
return new DigestScheme(challenge);
}
challenge = (String) challengemap.get("basic");
if (challenge != null) {
return new BasicScheme(challenge);
}
throw new UnsupportedOperationException(
"Authentication scheme(s) not supported: " + challengemap.toString());
}
private static boolean doAuthenticateDefault(
HttpMethod method,
HttpConnection conn,
HttpState state,
boolean proxy)
throws AuthenticationException {
if (method == null) {
throw new IllegalArgumentException("HTTP method may not be null");
}
if (state == null) {
throw new IllegalArgumentException("HTTP state may not be null");
}
String host = null;
if (conn != null) {
host = proxy ? conn.getProxyHost() : conn.getHost();
}
Credentials credentials = proxy
? state.getProxyCredentials(null, host) : state.getCredentials(null, host);
if (credentials == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("Default credentials for " + host + " not available");
}
return false;
}
if (!(credentials instanceof UsernamePasswordCredentials)) {
throw new AuthenticationException(
"Credentials cannot be used for basic authentication: "
+ credentials.toString());
}
String auth = BasicScheme.authenticate((UsernamePasswordCredentials) credentials);
if (auth != null) {
String s = proxy ? PROXY_AUTH_RESP : WWW_AUTH_RESP;
method.setRequestHeader(s, auth);
return true;
} else {
return false;
}
}
/**
* Attempt to provide default authentication credentials
* to the given method in the given context using basic
* authentication scheme.
*
* @param method the HttpMethod which requires authentication
* @param conn the connection to a specific host. This parameter
* may be <tt>null</tt> if default credentials (not specific
* to any particular host) are to be used
* @param state the HttpState object providing Credentials
*
* @return true if the <tt>Authenticate</tt> response header
* was added
*
* @throws AuthenticationException when a parsing or other error occurs
* @see HttpState#setCredentials(String,String,Credentials)
*/
public static boolean authenticateDefault(
HttpMethod method,
HttpConnection conn,
HttpState state)
throws AuthenticationException {
LOG.trace(
"enter HttpAuthenticator.authenticateDefault(HttpMethod, HttpConnection, HttpState)");
return doAuthenticateDefault(method, conn, state, false);
}
/**
* Attempt to provide default proxy authentication credentials
* to the given method in the given context using basic
* authentication scheme.
*
* @param method the HttpMethod which requires authentication
* @param conn the connection to a specific host. This parameter
* may be <tt>null</tt> if default credentials (not specific
* to any particular host) are to be used
* @param state the HttpState object providing Credentials
*
* @return true if the <tt>Proxy-Authenticate</tt> response header
* was added
*
* @throws AuthenticationException when a parsing or other error occurs
* @see HttpState#setCredentials(String,Credentials)
*/
public static boolean authenticateProxyDefault(
HttpMethod method,
HttpConnection conn,
HttpState state)
throws AuthenticationException {
LOG.trace("enter HttpAuthenticator.authenticateProxyDefault(HttpMethod, HttpState)");
return doAuthenticateDefault(method, conn, state, true);
}
private static boolean doAuthenticate(
AuthScheme authscheme,
HttpMethod method,
HttpConnection conn,
HttpState state,
boolean proxy)
throws AuthenticationException {
if (authscheme == null) {
throw new IllegalArgumentException("Authentication scheme may not be null");
}
if (method == null) {
throw new IllegalArgumentException("HTTP method may not be null");
}
if (state == null) {
throw new IllegalArgumentException("HTTP state may not be null");
}
String host = null;
if (conn != null) {
if (proxy) {
host = conn.getProxyHost();
} else {
host = conn.getVirtualHost();
if (host == null) {
host = conn.getHost();
}
}
}
String realm = authscheme.getRealm();
if (LOG.isDebugEnabled()) {
StringBuffer buffer = new StringBuffer();
buffer.append("Authenticating with the ");
if (realm == null) {
buffer.append("default");
} else {
buffer.append('\'');
buffer.append(realm);
buffer.append('\'');
}
buffer.append(" authentication realm at ");
buffer.append(host);
LOG.debug(buffer.toString());
}
// TODO: To be removed in the future. Required for backward compatibility
if (realm == null) {
realm = host;
}
Credentials credentials = proxy
? state.getProxyCredentials(realm, host)
: state.getCredentials(realm, host);
if (credentials == null) {
throw new AuthenticationException(
"No credentials available for the '" + authscheme.getRealm()
+ "' authentication realm at " + host);
}
String auth = authscheme.authenticate(credentials, method.getName(), method.getPath());
if (auth != null) {
String s = proxy ? PROXY_AUTH_RESP : WWW_AUTH_RESP;
method.setRequestHeader(s, auth);
return true;
} else {
return false;
}
}
/**
* Attempt to provide requisite authentication credentials to the
* given method in the given context using the given
* authentication scheme.
*
* @param authscheme The authentication scheme to be used
* @param method The HttpMethod which requires authentication
* @param conn the connection to a specific host. This parameter
* may be <tt>null</tt> if default credentials (not specific
* to any particular host) are to be used
* @param state The HttpState object providing Credentials
*
* @return true if the <tt>Authenticate</tt> response header was added
*
* @throws AuthenticationException when a parsing or other error occurs
* @see HttpState#setCredentials(String,Credentials)
*/
public static boolean authenticate(
AuthScheme authscheme,
HttpMethod method,
HttpConnection conn,
HttpState state)
throws AuthenticationException {
LOG.trace(
"enter HttpAuthenticator.authenticate(AuthScheme, HttpMethod, HttpConnection, "
+ "HttpState)");
return doAuthenticate(authscheme, method, conn, state, false);
}
/**
* Attempt to provide requisite proxy authentication credentials
* to the given method in the given context using
* the given authentication scheme.
*
* @param authscheme The authentication scheme to be used
* @param method the HttpMethod which requires authentication
* @param conn the connection to a specific host. This parameter
* may be <tt>null</tt> if default credentials (not specific
* to any particular host) are to be used
* @param state the HttpState object providing Credentials
*
* @return true if the <tt>Proxy-Authenticate</tt> response header
* was added
*
* @throws AuthenticationException when a parsing or other error occurs
* @see HttpState#setCredentials(String,Credentials)
*/
public static boolean authenticateProxy(
AuthScheme authscheme,
HttpMethod method,
HttpConnection conn,
HttpState state
) throws AuthenticationException {
LOG.trace("enter HttpAuthenticator.authenticateProxy(AuthScheme, HttpMethod, HttpState)");
return doAuthenticate(authscheme, method, conn, state, true);
}
}
|