Index: java/org/apache/commons/httpclient/ConnectMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v retrieving revision 1.22 diff -u -r1.22 ConnectMethod.java --- java/org/apache/commons/httpclient/ConnectMethod.java 10 Nov 2003 23:19:49 -0000 1.22 +++ java/org/apache/commons/httpclient/ConnectMethod.java 8 Dec 2003 19:49:56 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v 1.22 2003/11/10 23:19:49 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v 1.22 2003/11/10 23:19:49 olegk Exp $ * $Revision: 1.22 $ * $Date: 2003/11/10 23:19:49 $ * @@ -171,7 +171,6 @@ + "HttpConnection)"); addUserAgentRequestHeader(state, conn); addHostRequestHeader(state, conn); - addProxyAuthorizationRequestHeader(state, conn); addProxyConnectionHeader(state, conn); } Index: java/org/apache/commons/httpclient/HttpMethodBase.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.190 diff -u -r1.190 HttpMethodBase.java --- java/org/apache/commons/httpclient/HttpMethodBase.java 13 Nov 2003 22:24:46 -0000 1.190 +++ java/org/apache/commons/httpclient/HttpMethodBase.java 8 Dec 2003 19:50:08 -0000 @@ -68,12 +68,10 @@ import java.io.IOException; import java.io.InputStream; -import org.apache.commons.httpclient.auth.AuthScheme; -import org.apache.commons.httpclient.auth.HttpAuthenticator; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.cookie.CookieSpec; import org.apache.commons.httpclient.cookie.MalformedCookieException; -import org.apache.commons.httpclient.params.*; +import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.util.EncodingUtil; import org.apache.commons.logging.Log; @@ -146,12 +144,6 @@ /** Response trailer headers, if any. */ private HeaderGroup responseTrailerHeaders = new HeaderGroup(); - /** Actual authentication realm */ - private String realm = null; - - /** Actual proxy authentication realm */ - private String proxyRealm = null; - /** Path of the HTTP method. */ private String path = null; @@ -1027,8 +1019,6 @@ path = null; followRedirects = false; doAuthentication = true; - realm = null; - proxyRealm = null; queryString = null; getRequestHeaderGroup().clear(); getResponseHeaderGroup().clear(); @@ -1096,45 +1086,6 @@ /** - * Generates Authorization request header if needed, as long as no - * Authorization request header already exists. - * - * @param state the {@link HttpState state} information associated with this method - * @param conn the {@link HttpConnection connection} used to execute - * this HTTP method - * - * @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. - */ - protected void addAuthorizationRequestHeader(HttpState state, - HttpConnection conn) - throws IOException, HttpException { - LOG.trace("enter HttpMethodBase.addAuthorizationRequestHeader(" - + "HttpState, HttpConnection)"); - - // add authorization header, if needed - if (getRequestHeader(HttpAuthenticator.WWW_AUTH_RESP) == null) { - Header[] challenges = getResponseHeaderGroup().getHeaders( - HttpAuthenticator.WWW_AUTH); - if (challenges.length > 0) { - try { - AuthScheme authscheme = HttpAuthenticator.selectAuthScheme(challenges); - HttpAuthenticator.authenticate(authscheme, this, conn, state); - } catch (HttpException e) { - // log and move on - if (LOG.isErrorEnabled()) { - LOG.error(e.getMessage(), e); - } - } - } - } - } - - - - /** * Generates Cookie request headers for those {@link Cookie cookie}s * that match the given host, port and path. * @@ -1232,43 +1183,6 @@ } /** - * Generates Proxy-Authorization request header if needed, as long as no - * Proxy-Authorization request header already exists. - * - * @param state the {@link HttpState state} information associated with this method - * @param conn the {@link HttpConnection connection} used to execute - * this HTTP method - * - * @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. - */ - protected void addProxyAuthorizationRequestHeader(HttpState state, - HttpConnection conn) - throws IOException, HttpException { - LOG.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader(" - + "HttpState, HttpConnection)"); - - // add proxy authorization header, if needed - if (getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP) == null) { - Header[] challenges = getResponseHeaderGroup().getHeaders( - HttpAuthenticator.PROXY_AUTH); - if (challenges.length > 0) { - try { - AuthScheme authscheme = HttpAuthenticator.selectAuthScheme(challenges); - HttpAuthenticator.authenticateProxy(authscheme, this, conn, state); - } catch (HttpException e) { - // log and move on - if (LOG.isErrorEnabled()) { - LOG.error(e.getMessage(), e); - } - } - } - } - } - - /** * Generates Proxy-Connection: Keep-Alive request header when * communicating via a proxy server. * @@ -1326,8 +1240,6 @@ addUserAgentRequestHeader(state, conn); addHostRequestHeader(state, conn); addCookieRequestHeader(state, conn); - addAuthorizationRequestHeader(state, conn); - addProxyAuthorizationRequestHeader(state, conn); addProxyConnectionHeader(state, conn); } @@ -2155,7 +2067,7 @@ * @return proxy authentication realm */ public String getProxyAuthenticationRealm() { - return this.proxyRealm; + return null; } /** @@ -2167,7 +2079,7 @@ * @return authentication realm */ public String getAuthenticationRealm() { - return this.realm; + return null; } /** Index: java/org/apache/commons/httpclient/HttpMethodDirector.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v retrieving revision 1.10 diff -u -r1.10 HttpMethodDirector.java --- java/org/apache/commons/httpclient/HttpMethodDirector.java 19 Nov 2003 21:11:16 -0000 1.10 +++ java/org/apache/commons/httpclient/HttpMethodDirector.java 8 Dec 2003 19:50:13 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.10 2003/11/19 21:11:16 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.10 2003/11/19 21:11:16 olegk Exp $ * $Revision: 1.10 $ * $Date: 2003/11/19 21:11:16 $ * @@ -64,15 +64,22 @@ package org.apache.commons.httpclient; import java.io.IOException; +import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Set; +import org.apache.commons.httpclient.auth.AuthChallengeParser; +import org.apache.commons.httpclient.auth.AuthPolicy; import org.apache.commons.httpclient.auth.AuthScheme; import org.apache.commons.httpclient.auth.AuthenticationException; import org.apache.commons.httpclient.auth.CredentialsNotAvailableException; import org.apache.commons.httpclient.auth.HttpAuthenticator; import org.apache.commons.httpclient.auth.MalformedChallengeException; -import org.apache.commons.httpclient.params.*; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.apache.commons.httpclient.params.HttpParams; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -107,11 +114,19 @@ /** Proxy Realms that we tried to authenticate to */ private Set proxyRealms = null; - /** Actual authentication realm */ - private String realm = null; + /** Actual authentication scheme */ + private AuthScheme authScheme = null; - /** Actual proxy authentication realm */ - private String proxyRealm = null; + /** Actual proxy authentication scheme */ + private AuthScheme proxyAuthScheme = null; + + //TODO: to be parameterized + private static final List AUTH_PREFERENCES = new ArrayList(3); + static { + AUTH_PREFERENCES.add(AuthPolicy.NTLM); + AUTH_PREFERENCES.add(AuthPolicy.DIGEST); + AUTH_PREFERENCES.add(AuthPolicy.BASIC); + } public HttpMethodDirector( final HttpConnectionManager connectionManager, @@ -167,6 +182,7 @@ realms = new HashSet(); proxyRealms = new HashSet(); + cleanHeaders(method); addPreemtiveAuthenticationHeaders(method); } executeWithRetry(method); @@ -484,7 +500,9 @@ //invalidate the list of authentication attempts this.realms.clear(); //remove exisitng authentication headers - method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); + method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); + //Invalidate present authentication scheme + this.authScheme = null; //update the current location with the redirect location. //avoiding use of URL.getPath() and URL.getQuery() to keep //jdk1.2 comliance. @@ -515,108 +533,172 @@ + "HttpState, HttpConnection)"); boolean authenticated = false; - int statusCode = method.getStatusCode(); - // handle authentication required - Header[] challenges = null; - Set realmsUsed = null; - String host = null; - switch (statusCode) { - case HttpStatus.SC_UNAUTHORIZED: - challenges = method.getResponseHeaders(HttpAuthenticator.WWW_AUTH); - realmsUsed = realms; - host = this.conn.getVirtualHost(); - if (host == null) { - host = this.conn.getHost(); - } - break; - case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - challenges = method.getResponseHeaders(HttpAuthenticator.PROXY_AUTH); - realmsUsed = proxyRealms; - host = this.conn.getProxyHost(); - break; + try { + switch (method.getStatusCode()) { + case HttpStatus.SC_UNAUTHORIZED: + Map challenges = AuthChallengeParser.parseChallenges( + method.getResponseHeaders(HttpAuthenticator.WWW_AUTH)); + if (challenges.isEmpty()) { + return false; + } + if (this.authScheme != null) { + processChallenge(this.authScheme, challenges); + } else { + this.authScheme = processChallenge(challenges); + if (this.authScheme == null) { + return false; + } + } + String host = this.conn.getVirtualHost(); + if (host == null) { + host = this.conn.getHost(); + } + if (previousAttemptFailed(this.realms, host, + this.authScheme)) { + return false; + } + method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); + authenticated = HttpAuthenticator.authenticate( + this.authScheme, method, this.conn, this.state); + break; + case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: + Map proxyChallenges = AuthChallengeParser.parseChallenges( + method.getResponseHeaders(HttpAuthenticator.PROXY_AUTH)); + if (proxyChallenges.isEmpty()) { + return false; + } + if (this.proxyAuthScheme != null) { + processChallenge(this.proxyAuthScheme, proxyChallenges); + } else { + this.proxyAuthScheme = processChallenge(proxyChallenges); + if (this.proxyAuthScheme == null) { + return false; + } + } + if (previousAttemptFailed(this.proxyRealms, this.conn.getProxyHost(), + this.proxyAuthScheme)) { + return false; + } + method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); + authenticated = HttpAuthenticator.authenticateProxy( + proxyAuthScheme, method, this.conn, this.state); + break; + } + } catch (MalformedChallengeException e) { + if (LOG.isErrorEnabled()) { + LOG.error(e.getMessage(), e); + } + return false; + } catch (CredentialsNotAvailableException e) { + if (LOG.isWarnEnabled()) { + LOG.warn(e.getMessage()); + } + return false; + } catch (AuthenticationException e) { + if (LOG.isErrorEnabled()) { + LOG.error(e.getMessage(), e); + } + return false; } - // if there was a header requesting authentication - if (challenges.length > 0) { - AuthScheme authscheme = null; - try { - authscheme = HttpAuthenticator.selectAuthScheme(challenges); - } catch (MalformedChallengeException e) { - if (LOG.isErrorEnabled()) { - LOG.error(e.getMessage(), e); - } - return false; - } catch (UnsupportedOperationException e) { - if (LOG.isErrorEnabled()) { - LOG.error(e.getMessage(), e); - } - return false; - } - - StringBuffer buffer = new StringBuffer(); - buffer.append(host); - buffer.append('#'); - buffer.append(authscheme.getID()); - String realm = buffer.toString(); + if (!authenticated) { + // won't be able to authenticate to this challenge + // without additional information + LOG.debug("Credentials required to respond to authorization" + + " challenge are not available"); + } else { + LOG.debug("Credentials required to respond to authorization" + + " challenge have been provided"); + // let's try it again, using the credentials + } + return authenticated; + } - if (realmsUsed.contains(realm)) { + private void processChallenge(final AuthScheme authscheme, final Map challenges) + throws MalformedChallengeException, AuthenticationException + { + String id = authscheme.getSchemeName(); + if (LOG.isDebugEnabled()) { + LOG.debug("Using present authentication scheme: " + id); + } + String challenge = (String) challenges.get(id.toLowerCase()); + if (challenge == null) { + throw new AuthenticationException(id + + " authorization challenge expected, but not found"); + } + authscheme.processChallenge(challenge); + } + + private AuthScheme processChallenge(final Map challenges) + throws MalformedChallengeException, AuthenticationException { + AuthScheme authscheme = null; + String challenge = null; + if (LOG.isDebugEnabled()) { + LOG.debug("Supported authentication schemes in the order of preference: " + + AUTH_PREFERENCES); + } + Iterator item = AUTH_PREFERENCES.iterator(); + while (item.hasNext()) { + String id = (String) item.next(); + challenge = (String) challenges.get(id.toLowerCase()); + if (challenge != null) { if (LOG.isInfoEnabled()) { - buffer = new StringBuffer(); - buffer.append("Already tried to authenticate with '"); - buffer.append(authscheme.getRealm()); - buffer.append("' authentication realm at "); - buffer.append(host); - buffer.append(", but still receiving: "); - buffer.append(method.getStatusLine().toString()); - LOG.info(buffer.toString()); + LOG.info(id + " authentication scheme selected"); } - return false; + authscheme = AuthPolicy.getAuthScheme(id); + if (authscheme == null) { + throw new AuthenticationException("Requested authorization scheme " + + id + " is not supported"); + } + // Looks like we got something + break; } else { - realmsUsed.add(realm); + if (LOG.isDebugEnabled()) { + LOG.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + if (authscheme == null) { + // If none selected, something is wrong, warn and leave + if (LOG.isWarnEnabled()) { + LOG.warn("None of the following challenges can be responsed to: " + + challenges); } + } else { + // Process the challenge + authscheme.processChallenge(challenge); + } + return authscheme; + } - method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); - method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); - try { - //remove preemptive header and reauthenticate - switch (statusCode) { - case HttpStatus.SC_UNAUTHORIZED: - authenticated = HttpAuthenticator.authenticate( - authscheme, method, this.conn, this.state); - this.realm = authscheme.getRealm(); - break; - case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - authenticated = HttpAuthenticator.authenticateProxy( - authscheme, method, this.conn, this.state); - this.proxyRealm = authscheme.getRealm(); - break; - } - } catch (CredentialsNotAvailableException e) { - if (LOG.isWarnEnabled()) { - LOG.warn(e.getMessage()); - } - return false; // finished request - } catch (AuthenticationException e) { - if (LOG.isErrorEnabled()) { - LOG.error(e.getMessage(), e); - } - return false; // finished request - } - if (!authenticated) { - // won't be able to authenticate to this challenge - // without additional information - LOG.debug("HttpMethodBase.execute(): Server demands " - + "authentication credentials, but none are " - + "available, so aborting."); - } else { - LOG.debug("HttpMethodBase.execute(): Server demanded " - + "authentication credentials, will try again."); - // let's try it again, using the credentials - } - } - return authenticated; - } + private boolean previousAttemptFailed(final Set realms, final String host, final AuthScheme authscheme) { + // See if authentication against the given realm & host has + // already been attempted + StringBuffer buffer = new StringBuffer(); + buffer.append(host); + buffer.append('#'); + buffer.append(authscheme.getID()); + String realm = buffer.toString(); + + if (realms.contains(realm)) { + // Already tried. Give up + if (LOG.isInfoEnabled()) { + buffer = new StringBuffer(); + buffer.append("Attempt to authenticate with '"); + buffer.append(authscheme.getRealm()); + buffer.append("' authentication realm at "); + buffer.append(host); + buffer.append(" failed"); + LOG.info(buffer.toString()); + } + return true; + } else { + realms.add(realm); + return false; + } + } /** * Tests if the {@link HttpMethod method} requires a redirect to another location. * @@ -665,6 +747,15 @@ default: return false; } //end of switch + } + + private void cleanHeaders(HttpMethod method) { + if ((this.authScheme != null) && (this.authScheme.isConnectionBased())) { + method.removeRequestHeader(HttpAuthenticator.WWW_AUTH); + } + if ((this.proxyAuthScheme != null) && (this.proxyAuthScheme.isConnectionBased())) { + method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH); + } } /** Index: java/org/apache/commons/httpclient/auth/AuthChallengeParser.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthChallengeParser.java,v retrieving revision 1.6 diff -u -r1.6 AuthChallengeParser.java --- java/org/apache/commons/httpclient/auth/AuthChallengeParser.java 13 Jul 2003 21:29:05 -0000 1.6 +++ java/org/apache/commons/httpclient/auth/AuthChallengeParser.java 8 Dec 2003 19:50:14 -0000 @@ -67,6 +67,7 @@ import java.util.List; import java.util.Map; +import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.util.ParameterParser; @@ -139,4 +140,30 @@ } return map; } + + /** + * Extracts a map of challenges ordered by authentication scheme name + * + * @param headers the array of authorization challenges + * @return a map of authorization challenges + * + * @throws MalformedChallengeException if any of challenge strings + * is malformed + * + * @since 2.0beta1 + */ + public static Map parseChallenges(final Header[] headers) + throws MalformedChallengeException { + if (headers == null) { + throw new IllegalArgumentException("Array of challenges may not be null"); + } + String challenge = null; + Map challengemap = new HashMap(headers.length); + for (int i = 0; i < headers.length; i++) { + challenge = headers[i].getValue(); + String s = AuthChallengeParser.extractScheme(challenge); + challengemap.put(s, challenge); + } + return challengemap; + } } Index: java/org/apache/commons/httpclient/auth/AuthPolicy.java =================================================================== RCS file: java/org/apache/commons/httpclient/auth/AuthPolicy.java diff -N java/org/apache/commons/httpclient/auth/AuthPolicy.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/auth/AuthPolicy.java 8 Dec 2003 19:50:15 -0000 @@ -0,0 +1,185 @@ +/* + * $Header$ + * $Revision$ + * $Date$ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * 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 + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ + +package org.apache.commons.httpclient.auth; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Authentication policy class. The Authentication policy provides corresponding + * authentication scheme interfrace for a given type of authorization challenge. + *

The following specifications are provided: + *

+ * + * @author Oleg Kalnichevski + * + * @version $Revision$ + * @since 2.1 + */ +public abstract class AuthPolicy { + + private static Map SCHEMES = Collections.synchronizedMap(new HashMap()); + + /** + * The NTLM scheme is a proprietary Microsoft Windows Authentication + * protocol (considered to be the most secure among currently supported + * authentication schemes). + */ + public static final String NTLM = "NTLM"; + + /** + * Digest authentication scheme as defined in RFC2617. + */ + public static final String DIGEST = "Digest"; + + /** + * Basic authentication scheme as defined in RFC2617 (considered inherently + * insecure, but most widely supported) + */ + public static final String BASIC = "Basic"; + + static { + AuthPolicy.registerAuthScheme(BASIC, BasicScheme.class); + AuthPolicy.registerAuthScheme(DIGEST, DigestScheme.class); + AuthPolicy.registerAuthScheme(NTLM, NTLMScheme.class); + } + + /** Log object. */ + protected static final Log LOG = LogFactory.getLog(AuthPolicy.class); + + /** + * Registers a class implementing an {@link AuthScheme authentication scheme} with + * the given identifier. If a class with the given ID already exists it will be overridden. + * This ID is the same one used to retrieve the {@link AuthScheme authentication scheme} + * from {@link #getAuthScheme(String)}. + * + * @param id the identifier for this scheme + * @param clazz the class to register + * + * @see #getAuthScheme(String) + */ + public static void registerAuthScheme(final String id, Class clazz) { + if (id == null) { + throw new IllegalArgumentException("Id may not be null"); + } + if (clazz == null) { + throw new IllegalArgumentException("Authentication scheme class may not be null"); + } + SCHEMES.put(id.toLowerCase(), clazz); + } + + /** + * Unregisters the class implementing an {@link AuthScheme authentication scheme} with + * the given ID. + * + * @param id the ID of the class to unregister + */ + public static void unregisterAuthScheme(final String id) { + if (id == null) { + throw new IllegalArgumentException("Id may not be null"); + } + SCHEMES.remove(id.toLowerCase()); + } + + /** + * Gets the {@link AuthScheme authentication scheme} with the given ID. + * + * @param id the {@link AuthScheme authentication scheme} ID + * + * @return {@link AuthScheme authentication scheme} + * + * @throws IllegalStateException if a scheme with the ID cannot be found + */ + public static AuthScheme getAuthScheme(final String id) + throws IllegalStateException { + + if (id == null) { + throw new IllegalArgumentException("Id may not be null"); + } + Class clazz = (Class)SCHEMES.get(id.toLowerCase()); + if (clazz != null) { + try { + return (AuthScheme)clazz.newInstance(); + } catch (Exception e) { + LOG.error("Error initializing authentication scheme: " + id, e); + throw new IllegalStateException(id + + " authentication scheme implemented by " + + clazz.getName() + " could not be initialized"); + } + } else { + return null; + } + } +} Index: java/org/apache/commons/httpclient/auth/AuthScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthScheme.java,v retrieving revision 1.4 diff -u -r1.4 AuthScheme.java --- java/org/apache/commons/httpclient/auth/AuthScheme.java 22 Apr 2003 17:00:25 -0000 1.4 +++ java/org/apache/commons/httpclient/auth/AuthScheme.java 8 Dec 2003 19:50:16 -0000 @@ -67,23 +67,31 @@ /** *

- * This interface represents an abstract authentication scheme. + * This interface represents an abstract challenge-response oriented + * authentication scheme. *

*

* An authentication scheme should be able to support the following * functions: *

*

*

* Authentication schemes may ignore method name and URI parameters - * if they are relevant for the given authentication mechanism + * if they are not relevant for the given authentication mechanism + *

+ *

+ * Authentication schemes may be stateful involving a series of + * challenge-response exchanges *

* * @author Oleg Kalnichevski @@ -93,6 +101,15 @@ */ public interface AuthScheme { + + /** + * Processes the given challenge token. Some authentication schemes + * may involve multiple challenge-response exchanges. Such schemes must be able + * to maintain the state information when dealing with sequential challenges + * + * @param the challenge string + */ + void processChallenge(final String challenge) throws MalformedChallengeException; /** * Returns textual designation of the given authentication scheme. @@ -136,10 +153,20 @@ * returned value may be null. */ String getID(); + + /** + * Tests if the authentication scheme is provides authorization on a per + * connection basis instead of usual per request basis + * + * @return true if the scheme is connection based, false + * if the scheme is request based. + */ + boolean isConnectionBased(); /** * Produces an authorization string for the given set of {@link Credentials}, - * method name and URI using the given authentication scheme. + * method name and URI using the given authentication scheme in response to + * the actual authorization challenge. * * @param credentials The set of credentials to be used for athentication * @param method The name of the method that requires authorization. Index: java/org/apache/commons/httpclient/auth/AuthSchemeBase.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthSchemeBase.java,v retrieving revision 1.3 diff -u -r1.3 AuthSchemeBase.java --- java/org/apache/commons/httpclient/auth/AuthSchemeBase.java 6 Apr 2003 22:31:53 -0000 1.3 +++ java/org/apache/commons/httpclient/auth/AuthSchemeBase.java 8 Dec 2003 19:50:17 -0000 @@ -68,6 +68,7 @@ * Abstract authentication scheme class that implements {@link AuthScheme} * interface and provides a default contstructor. *

+ * @deprecated No longer used * * @author Oleg Kalnichevski */ @@ -85,6 +86,9 @@ * * @throws MalformedChallengeException is thrown if the authentication challenge * is malformed + * + * @deprecated Use parameterless constructor and {@link AuthScheme#challenge(String)} + * method */ public AuthSchemeBase(final String challenge) throws MalformedChallengeException { Index: java/org/apache/commons/httpclient/auth/BasicScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/BasicScheme.java,v retrieving revision 1.7 diff -u -r1.7 BasicScheme.java --- java/org/apache/commons/httpclient/auth/BasicScheme.java 14 Nov 2003 02:28:49 -0000 1.7 +++ java/org/apache/commons/httpclient/auth/BasicScheme.java 8 Dec 2003 19:50:18 -0000 @@ -91,12 +91,23 @@ private static final Log LOG = LogFactory.getLog(BasicScheme.class); /** + * Default constructor for the basic authetication scheme. + * + */ + public BasicScheme() { + super(); + } + + /** * Constructor for the basic authetication scheme. * * @param challenge authentication challenge * * @throws MalformedChallengeException is thrown if the authentication challenge * is malformed + * + * @deprecated Use parameterless constructor and {@link AuthScheme#challenge(String)} + * method */ public BasicScheme(final String challenge) throws MalformedChallengeException { super(challenge); @@ -140,6 +151,15 @@ + credentials.getClass().getName()); } return BasicScheme.authenticate(usernamepassword); + } + + /** + * Returns false. Basic authentication scheme is request based. + * + * @return false. + */ + public boolean isConnectionBased() { + return false; } /** Index: java/org/apache/commons/httpclient/auth/DigestScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v retrieving revision 1.11 diff -u -r1.11 DigestScheme.java --- java/org/apache/commons/httpclient/auth/DigestScheme.java 3 Oct 2003 20:57:36 -0000 1.11 +++ java/org/apache/commons/httpclient/auth/DigestScheme.java 8 Dec 2003 19:50:21 -0000 @@ -120,6 +120,14 @@ private String cnonce; /** + * Default constructor for the digest authetication scheme. + * + */ + public DigestScheme() { + super(); + } + + /** * Gets an ID based upon the realm and the nonce value. This ensures that requests * to the same realm with different nonce values will succeed. This differentiation * allows servers to request re-authentication using a fresh nonce value. @@ -136,12 +144,15 @@ } /** - * Constructor for the digest authentication scheme. + * Constructor for the digest authetication scheme. * - * @param challenge The authentication challenge + * @param challenge authentication challenge * * @throws MalformedChallengeException is thrown if the authentication challenge * is malformed + * + * @deprecated Use parameterless constructor and {@link AuthScheme#challenge(String)} + * method */ public DigestScheme(final String challenge) throws MalformedChallengeException { @@ -177,6 +188,48 @@ cnonce = createCnonce(); } + /** + * Processes the Digest challenge. + * + * @param the challenge string + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public void processChallenge(final String challenge) + throws MalformedChallengeException { + super.processChallenge(challenge); + + if (getParameter("nonce") == null) { + throw new MalformedChallengeException("missing nonce in challange"); + } + + boolean unsupportedQop = false; + // qop parsing + String qop = getParameter("qop"); + if (qop != null) { + StringTokenizer tok = new StringTokenizer(qop,","); + while (tok.hasMoreTokens()) { + String variant = tok.nextToken().trim(); + if (variant.equals("auth")) { + qopVariant = QOP_AUTH; + break; //that's our favourite, because auth-int is unsupported + } else if (variant.equals("auth-int")) { + qopVariant = QOP_AUTH_INT; + } else { + unsupportedQop = true; + LOG.warn("Unsupported qop detected: "+ variant); + } + } + } + + if (unsupportedQop && (qopVariant == QOP_MISSING)) { + throw new MalformedChallengeException("None of the qop methods is supported"); + } + + cnonce = createCnonce(); + } + /** * Returns textual designation of the digest authentication scheme. @@ -185,6 +238,15 @@ */ public String getSchemeName() { return "digest"; + } + + /** + * Returns false. Digest authentication scheme is request based. + * + * @return false. + */ + public boolean isConnectionBased() { + return false; } /** Index: java/org/apache/commons/httpclient/auth/HttpAuthenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java,v retrieving revision 1.13 diff -u -r1.13 HttpAuthenticator.java --- java/org/apache/commons/httpclient/auth/HttpAuthenticator.java 2 Nov 2003 12:10:28 -0000 1.13 +++ java/org/apache/commons/httpclient/auth/HttpAuthenticator.java 8 Dec 2003 19:50:23 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java,v 1.13 2003/11/02 12:10:28 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java,v 1.13 2003/11/02 12:10:28 olegk Exp $ * $Revision: 1.13 $ * $Date: 2003/11/02 12:10:28 $ * @@ -63,12 +63,13 @@ package org.apache.commons.httpclient.auth; -import java.util.Map; import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.httpclient.Credentials; 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; @@ -110,19 +111,16 @@ */ 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. */ @@ -145,6 +143,9 @@ * challenge is malformed * @throws UnsupportedOperationException when none of challenge types * available is supported. + * + * @deprecated Use {@link AuthChallengeParser#parseChallenges(Header[])} and + * {@link AuthPolicy#getAuthScheme(String)} */ public static AuthScheme selectAuthScheme(final Header[] challenges) throws MalformedChallengeException { Index: java/org/apache/commons/httpclient/auth/NTLMScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java,v retrieving revision 1.11 diff -u -r1.11 NTLMScheme.java --- java/org/apache/commons/httpclient/auth/NTLMScheme.java 16 Aug 2003 00:41:24 -0000 1.11 +++ java/org/apache/commons/httpclient/auth/NTLMScheme.java 8 Dec 2003 19:50:25 -0000 @@ -81,7 +81,7 @@ * @author Mike Bowler * @author Oleg Kalnichevski */ -public class NTLMScheme extends AuthSchemeBase { +public class NTLMScheme implements AuthScheme { /** Log object for this class. */ private static final Log LOG = LogFactory.getLog(NTLMScheme.class); @@ -90,6 +90,14 @@ private String ntlmchallenge = null; /** + * Default constructor for the NTLM authentication scheme. + * + */ + public NTLMScheme() { + super(); + } + + /** * Constructor for the NTLM authentication scheme. * * @param challenge The authentication challenge @@ -98,7 +106,19 @@ * is malformed */ public NTLMScheme(final String challenge) throws MalformedChallengeException { - super(challenge); + super(); + processChallenge(challenge); + } + + /** + * Processes the NTLM challenge. + * + * @param the challenge string + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public void processChallenge(final String challenge) throws MalformedChallengeException { String s = AuthChallengeParser.extractScheme(challenge); if (!s.equalsIgnoreCase(getSchemeName())) { throw new MalformedChallengeException("Invalid NTLM challenge: " + challenge); @@ -167,6 +187,15 @@ throw new IllegalArgumentException("Parameter name may not be null"); } return null; + } + + /** + * Returns true. NTLM authentication scheme is connection based. + * + * @return true. + */ + public boolean isConnectionBased() { + return true; } /** Index: java/org/apache/commons/httpclient/auth/RFC2617Scheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/RFC2617Scheme.java,v retrieving revision 1.4 diff -u -r1.4 RFC2617Scheme.java --- java/org/apache/commons/httpclient/auth/RFC2617Scheme.java 22 Apr 2003 17:00:25 -0000 1.4 +++ java/org/apache/commons/httpclient/auth/RFC2617Scheme.java 8 Dec 2003 19:50:26 -0000 @@ -74,7 +74,7 @@ * * @author Oleg Kalnichevski */ -public abstract class RFC2617Scheme extends AuthSchemeBase { +public abstract class RFC2617Scheme implements AuthScheme { /** * Authentication parameter map. @@ -84,13 +84,38 @@ /** * Default constructor for RFC2617 compliant authetication schemes. * + */ + public RFC2617Scheme() { + super(); + } + + /** + * Default constructor for RFC2617 compliant authetication schemes. + * * @param challenge authentication challenge * * @throws MalformedChallengeException is thrown if the authentication challenge * is malformed + * + * @deprecated Use parameterless constructor and {@link AuthScheme#challenge(String)} + * method */ public RFC2617Scheme(final String challenge) throws MalformedChallengeException { - super(challenge); + super(); + processChallenge(challenge); + } + + /** + * Processes the given challenge token. Some authentication schemes + * may involve multiple challenge-response exchanges. Such schemes must be able + * to maintain the state information when dealing with sequential challenges + * + * @param the challenge string + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public void processChallenge(final String challenge) throws MalformedChallengeException { String s = AuthChallengeParser.extractScheme(challenge); if (!s.equalsIgnoreCase(getSchemeName())) { throw new MalformedChallengeException(