olegk 2003/12/10 13:04:13 Modified: httpclient/src/java/org/apache/commons/httpclient ConnectMethod.java HttpMethodBase.java HttpMethodDirector.java httpclient/src/java/org/apache/commons/httpclient/auth AuthChallengeParser.java AuthScheme.java AuthSchemeBase.java BasicScheme.java DigestScheme.java HttpAuthenticator.java NTLMScheme.java RFC2617Scheme.java httpclient/src/test/org/apache/commons/httpclient TestAuthenticator.java httpclient/src/test/org/apache/commons/httpclient/server ProxyAuthRequestHandler.java Log: Changelog: * Another attempt at fixing NTLM proxy + basic host authentication (R: #24352) * Plug-in mechanism for authentication modules * AuthModule interface implementing authentication modules can now be instantiated using default (parameter-less) constructor * Authentication modules can now retain limited state information (the state is retained within the lifetime of the method director) * Authentication scheme selection routine can be easily parameterized * Yet another massive refactoring of HttpMethodDirector Contributed by Oleg Kalnichevski Reviewed By Michael Becke Revision Changes Path 1.23 +4 -5 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java Index: ConnectMethod.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v retrieving revision 1.22 retrieving revision 1.23 diff -u -r1.22 -r1.23 --- ConnectMethod.java 10 Nov 2003 23:19:49 -0000 1.22 +++ ConnectMethod.java 10 Dec 2003 21:04:13 -0000 1.23 @@ -171,7 +171,6 @@ + "HttpConnection)"); addUserAgentRequestHeader(state, conn); addHostRequestHeader(state, conn); - addProxyAuthorizationRequestHeader(state, conn); addProxyConnectionHeader(state, conn); } 1.191 +7 -95 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java Index: HttpMethodBase.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.190 retrieving revision 1.191 diff -u -r1.190 -r1.191 --- HttpMethodBase.java 13 Nov 2003 22:24:46 -0000 1.190 +++ HttpMethodBase.java 10 Dec 2003 21:04:13 -0000 1.191 @@ -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 <tt>Authorization</tt> request header if needed, as long as no - * <tt>Authorization</tt> request header already exists. - * - * @param state the [EMAIL PROTECTED] HttpState state} information associated with this method - * @param conn the [EMAIL PROTECTED] 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 <tt>Cookie</tt> request headers for those [EMAIL PROTECTED] Cookie cookie}s * that match the given host, port and path. * @@ -1232,43 +1183,6 @@ } /** - * Generates <tt>Proxy-Authorization</tt> request header if needed, as long as no - * <tt>Proxy-Authorization</tt> request header already exists. - * - * @param state the [EMAIL PROTECTED] HttpState state} information associated with this method - * @param conn the [EMAIL PROTECTED] 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 <tt>Proxy-Connection: Keep-Alive</tt> 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; } /** 1.11 +192 -102 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java Index: HttpMethodDirector.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- HttpMethodDirector.java 19 Nov 2003 21:11:16 -0000 1.10 +++ HttpMethodDirector.java 10 Dec 2003 21:04:13 -0000 1.11 @@ -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, @@ -484,7 +499,12 @@ //invalidate the list of authentication attempts this.realms.clear(); //remove exisitng authentication headers - method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); + if ((this.proxyAuthScheme != null) && (this.proxyAuthScheme.isConnectionBased())) { + method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH); + } + 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. @@ -514,109 +534,179 @@ LOG.trace("enter HttpMethodBase.processAuthenticationResponse(" + "HttpState, HttpConnection)"); + if ((this.proxyAuthScheme != null) && (this.proxyAuthScheme.isConnectionBased())) { + method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH); + } + if ((this.authScheme != null) && (this.authScheme.isConnectionBased())) { + method.removeRequestHeader(HttpAuthenticator.WWW_AUTH); + } 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"); + } + authscheme = AuthPolicy.getAuthScheme(id); + if (authscheme == null) { + throw new AuthenticationException("Requested authorization scheme " + + id + " is not supported"); } - return false; + // 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 [EMAIL PROTECTED] HttpMethod method} requires a redirect to another location. * 1.7 +30 -3 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthChallengeParser.java Index: AuthChallengeParser.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthChallengeParser.java,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- AuthChallengeParser.java 13 Jul 2003 21:29:05 -0000 1.6 +++ AuthChallengeParser.java 10 Dec 2003 21:04:13 -0000 1.7 @@ -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; + } } 1.5 +33 -6 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthScheme.java Index: AuthScheme.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthScheme.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- AuthScheme.java 22 Apr 2003 17:00:25 -0000 1.4 +++ AuthScheme.java 10 Dec 2003 21:04:13 -0000 1.5 @@ -67,23 +67,31 @@ /** * <p> - * This interface represents an abstract authentication scheme. + * This interface represents an abstract challenge-response oriented + * authentication scheme. * </p> * <p> * An authentication scheme should be able to support the following * functions: * <ul> + * <li>Parse and process the challenge sent by the targer server + * in response to request for a protected resource * <li>Provide its textual designation * <li>Provide its parameters, if available * <li>Provide the realm this authentication scheme is applicable to, * if available * <li>Generate authorization string for the given set of credentials, * request method and URI as specificed in the HTTP request line + * in response to the actual authorization challenge * </ul> * </p> * <p> * 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 + * </p> + * <p> + * Authentication schemes may be stateful involving a series of + * challenge-response exchanges * </p> * * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> @@ -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 <tt>true</tt> if the scheme is connection based, <tt>false</tt> + * if the scheme is request based. + */ + boolean isConnectionBased(); /** * Produces an authorization string for the given set of [EMAIL PROTECTED] 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. 1.4 +7 -3 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthSchemeBase.java Index: AuthSchemeBase.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthSchemeBase.java,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- AuthSchemeBase.java 6 Apr 2003 22:31:53 -0000 1.3 +++ AuthSchemeBase.java 10 Dec 2003 21:04:13 -0000 1.4 @@ -68,6 +68,7 @@ * Abstract authentication scheme class that implements [EMAIL PROTECTED] AuthScheme} * interface and provides a default contstructor. * </p> + * @deprecated No longer used * * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> */ @@ -85,6 +86,9 @@ * * @throws MalformedChallengeException is thrown if the authentication challenge * is malformed + * + * @deprecated Use parameterless constructor and [EMAIL PROTECTED] AuthScheme#challenge(String)} + * method */ public AuthSchemeBase(final String challenge) throws MalformedChallengeException { 1.8 +23 -3 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/BasicScheme.java Index: BasicScheme.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/BasicScheme.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- BasicScheme.java 14 Nov 2003 02:28:49 -0000 1.7 +++ BasicScheme.java 10 Dec 2003 21:04:13 -0000 1.8 @@ -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 [EMAIL PROTECTED] 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 <tt>false</tt>. Basic authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; } /** 1.12 +67 -5 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java Index: DigestScheme.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- DigestScheme.java 3 Oct 2003 20:57:36 -0000 1.11 +++ DigestScheme.java 10 Dec 2003 21:04:13 -0000 1.12 @@ -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 [EMAIL PROTECTED] 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 <tt>false</tt>. Digest authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; } /** 1.14 +9 -8 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java Index: HttpAuthenticator.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java,v retrieving revision 1.13 retrieving revision 1.14 diff -u -r1.13 -r1.14 --- HttpAuthenticator.java 2 Nov 2003 12:10:28 -0000 1.13 +++ HttpAuthenticator.java 10 Dec 2003 21:04:13 -0000 1.14 @@ -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 [EMAIL PROTECTED] AuthChallengeParser#parseChallenges(Header[])} and + * [EMAIL PROTECTED] AuthPolicy#getAuthScheme(String)} */ public static AuthScheme selectAuthScheme(final Header[] challenges) throws MalformedChallengeException { 1.12 +34 -5 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java Index: NTLMScheme.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- NTLMScheme.java 16 Aug 2003 00:41:24 -0000 1.11 +++ NTLMScheme.java 10 Dec 2003 21:04:13 -0000 1.12 @@ -81,7 +81,7 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a> * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> */ -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 <tt>true</tt>. NTLM authentication scheme is connection based. + * + * @return <tt>true</tt>. + */ + public boolean isConnectionBased() { + return true; } /** 1.5 +30 -5 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/RFC2617Scheme.java Index: RFC2617Scheme.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/RFC2617Scheme.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- RFC2617Scheme.java 22 Apr 2003 17:00:25 -0000 1.4 +++ RFC2617Scheme.java 10 Dec 2003 21:04:13 -0000 1.5 @@ -74,7 +74,7 @@ * * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> */ -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 [EMAIL PROTECTED] 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( 1.35 +44 -35 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java Index: TestAuthenticator.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v retrieving revision 1.34 retrieving revision 1.35 diff -u -r1.34 -r1.35 --- TestAuthenticator.java 24 Nov 2003 22:17:02 -0000 1.34 +++ TestAuthenticator.java 10 Dec 2003 21:04:13 -0000 1.35 @@ -131,7 +131,8 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new BasicScheme(challenge); + AuthScheme authscheme = new BasicScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, state); fail("Should have thrown HttpException"); } catch(HttpException e) { @@ -144,7 +145,8 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new BasicScheme(challenge); + AuthScheme authscheme = new BasicScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, state); fail("Should have thrown HttpException"); } catch(HttpException e) { @@ -157,7 +159,8 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new BasicScheme(challenge); + AuthScheme authscheme = new BasicScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, state); fail("Should have thrown HttpException"); } catch(HttpException e) { @@ -169,7 +172,8 @@ String challenge = "Basic realm=\"realm1\""; HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new BasicScheme(challenge); + AuthScheme authscheme = new BasicScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, null); fail("Should have thrown IllegalArgumentException"); } catch(IllegalArgumentException e) { @@ -177,23 +181,13 @@ } } - public void testInvalidAuthenticationScheme() throws Exception { - String challenge = "invalid realm=\"realm1\""; - try{ - HttpAuthenticator.selectAuthScheme( - new Header[] { new Header("WWW-Authenticate", challenge) }); - fail("Should have thrown UnsupportedOperationException"); - }catch (UnsupportedOperationException uoe){ - // expected - } - } - public void testBasicAuthenticationCaseInsensitivity() throws Exception { String challenge = "bAsIc ReAlM=\"realm1\""; HttpState state = new HttpState(); state.setCredentials(null, null, new UsernamePasswordCredentials("username","password")); HttpMethod method = new SimpleHttpMethod(new Header("WwW-AuThEnTiCaTe", challenge)); - AuthScheme authscheme = new BasicScheme(challenge); + AuthScheme authscheme = new BasicScheme(); + authscheme.processChallenge(challenge); assertTrue(HttpAuthenticator.authenticate(authscheme, method, null, state)); assertTrue(null != method.getRequestHeader("Authorization")); String expected = "Basic " + HttpConstants.getString(Base64.encode(HttpConstants.getBytes("username:password"))); @@ -216,7 +210,8 @@ HttpState state = new HttpState(); state.setCredentials("realm", null, new UsernamePasswordCredentials("username","password")); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); - AuthScheme authscheme = new BasicScheme(challenge); + AuthScheme authscheme = new BasicScheme(); + authscheme.processChallenge(challenge); assertTrue(HttpAuthenticator.authenticate(authscheme, method, null, state)); assertTrue(null != method.getRequestHeader("Authorization")); String expected = "Basic " + HttpConstants.getString(Base64.encode(HttpConstants.getBytes("username:password"))); @@ -240,8 +235,10 @@ HttpState state = new HttpState(); state.setCredentials("realm1", null, new UsernamePasswordCredentials("username","password")); state.setCredentials("realm2", null, new UsernamePasswordCredentials("uname2","password2")); - AuthScheme authscheme1 = new BasicScheme(challenge1); - AuthScheme authscheme2 = new BasicScheme(challenge2); + AuthScheme authscheme1 = new BasicScheme(); + authscheme1.processChallenge(challenge1); + AuthScheme authscheme2 = new BasicScheme(); + authscheme2.processChallenge(challenge2); { HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate",challenge1)); assertTrue(HttpAuthenticator.authenticate(authscheme1, method, null, state)); @@ -284,7 +281,8 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, state); fail("Should have thrown HttpException"); } catch(HttpException e) { @@ -297,7 +295,8 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, state); fail("Should have thrown HttpException"); } catch(HttpException e) { @@ -310,7 +309,8 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, state); fail("Should have thrown HttpException"); } catch(HttpException e) { @@ -322,7 +322,8 @@ String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\""; HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); try { - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); HttpAuthenticator.authenticate(authscheme, method, null, null); fail("Should have thrown IllegalArgumentException"); } catch(IllegalArgumentException e) { @@ -336,7 +337,8 @@ UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password"); state.setCredentials(null, null, cred); HttpMethod method = new SimpleHttpMethod(new Header("WwW-AuThEnTiCaTe", challenge)); - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); assertTrue(HttpAuthenticator.authenticate(authscheme, method, null, state)); assertTrue(null != method.getRequestHeader("Authorization")); } @@ -348,7 +350,8 @@ UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password"); state.setCredentials(null, null, cred); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); assertTrue(HttpAuthenticator.authenticate(authscheme, method, null, state)); assertTrue(null != method.getRequestHeader("Authorization")); Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue()); @@ -365,7 +368,8 @@ UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password"); state.setCredentials(null, null, cred); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); assertTrue(HttpAuthenticator.authenticate(authscheme, method, null, state)); assertTrue(null != method.getRequestHeader("Authorization")); Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue()); @@ -423,8 +427,10 @@ state.setCredentials("realm1", null, cred); UsernamePasswordCredentials cred2 = new UsernamePasswordCredentials("uname2","password2"); state.setCredentials("realm2", null, cred2); - AuthScheme authscheme1 = new DigestScheme(challenge1); - AuthScheme authscheme2 = new DigestScheme(challenge2); + AuthScheme authscheme1 = new DigestScheme(); + authscheme1.processChallenge(challenge1); + AuthScheme authscheme2 = new DigestScheme(); + authscheme2.processChallenge(challenge2); { HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate",challenge1)); assertTrue(HttpAuthenticator.authenticate(authscheme1, method, null, state)); @@ -471,7 +477,8 @@ UsernamePasswordCredentials cred = new UsernamePasswordCredentials(username, password); state.setCredentials(realm, null, cred); - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); assertTrue(HttpAuthenticator.authenticate( @@ -512,7 +519,8 @@ UsernamePasswordCredentials cred = new UsernamePasswordCredentials(username, password); state.setCredentials(realm, null, cred); - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge)); assertTrue(HttpAuthenticator.authenticate( @@ -554,7 +562,8 @@ new UsernamePasswordCredentials(username, password); state.setCredentials(realm, null, cred); try { - AuthScheme authscheme = new DigestScheme(challenge); + AuthScheme authscheme = new DigestScheme(); + authscheme.processChallenge(challenge); fail("MalformedChallengeException exception expected due to invalid qop value"); } catch(MalformedChallengeException e) { /* expected */ 1.3 +5 -4 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/ProxyAuthRequestHandler.java Index: ProxyAuthRequestHandler.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/ProxyAuthRequestHandler.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- ProxyAuthRequestHandler.java 5 Dec 2003 21:23:15 -0000 1.2 +++ ProxyAuthRequestHandler.java 10 Dec 2003 21:04:13 -0000 1.3 @@ -142,7 +142,8 @@ // TODO Auto-generated method stub BasicScheme scheme; try { - scheme = new BasicScheme("basic realm=test"); + scheme = new BasicScheme(); + scheme.processChallenge("basic realm=test"); String expectedAuthString = scheme.authenticate(credentials, null, null); return expectedAuthString.equals(clientAuth.getValue()); } catch (MalformedChallengeException e) {
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]