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
+ *
The following specifications are provided: + *
- * 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(