? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth ? java/org/apache/commons/httpclient/auth Index: java/org/apache/commons/httpclient/AuthChallengeParser.java =================================================================== RCS file: java/org/apache/commons/httpclient/AuthChallengeParser.java diff -N java/org/apache/commons/httpclient/AuthChallengeParser.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/AuthChallengeParser.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,232 @@ +/* + * ==================================================================== + * + * 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.Map; +import java.util.HashMap; +import java.util.StringTokenizer; + +/** + * This class provides utility methods for parsing HTTP www and proxy authentication + * challenges. + * + * @author Oleg Kalnichevski + * + * @since 2.0beta1 + */ +public final class AuthChallengeParser +{ + /** + * Extracts authentication scheme from the given authentication + * challenge. + * + * @param challengeStr the authentication challenge string + * @return authentication scheme + * + * @throws MalformedChallengeException when the authentication challenge string + * is malformed + * + * @since 2.0beta1 + */ + public static String extractScheme(final String challengeStr) + throws MalformedChallengeException { + if (challengeStr == null) { + throw new IllegalArgumentException("Challenge may not be null"); + } + int i = challengeStr.indexOf(' '); + String s = null; + if (i == -1) { + s = challengeStr; + } else { + s = challengeStr.substring(0, i); + } + if (s.equals("")) { + throw new MalformedChallengeException( "Invalid challenge: " + challengeStr); + } + return s.toLowerCase(); + } + + /** + * Extracts a map of challenge parameters from an authentication challenge. + * Keys in the map are lower-cased + * + * @param challengeStr the authentication challenge string + * @return a map of authentication challenge parameters + * @throws MalformedChallengeException when the authentication challenge string + * is malformed + * + * @since 2.0beta1 + */ + public static Map extractParams(final String challengeStr) + throws MalformedChallengeException { + if (challengeStr == null) { + throw new IllegalArgumentException("Challenge may not be null"); + } + int i = challengeStr.indexOf(' '); + if (i == -1) { + throw new MalformedChallengeException( "Invalid challenge: " + challengeStr ); + } + + Map elements = new HashMap(); + + i++; + int len = challengeStr.length(); + + String name = null; + String value = null; + + StringBuffer buffer = new StringBuffer(); + + boolean parsingName = true; + boolean inQuote = false; + boolean gotIt = false; + + while (i < len) { + // Parse one char at a time + char ch = challengeStr.charAt(i); + i++; + // Process the char + if (parsingName) { + // parsing name + if (ch == '=') { + name = buffer.toString().trim(); + parsingName = false; + buffer.setLength(0); + } else if (ch == ',') { + name = buffer.toString().trim(); + value = null; + gotIt = true; + buffer.setLength(0); + } else { + buffer.append(ch); + } + // Have I reached the end of the challenge string? + if (i == len) { + name = buffer.toString().trim(); + value = null; + gotIt = true; + } + } else { + //parsing value + if (!inQuote) { + // Value is not quoted or not found yet + if (ch == ',') { + value = buffer.toString().trim(); + gotIt = true; + buffer.setLength(0); + } else { + // no value yet + if (buffer.length() == 0) { + if (ch == ' ') { + //discard + } else if (ch == '\t') { + //discard + } else if (ch == '\n') { + //discard + } else if (ch == '\r') { + //discard + } else { + // otherwise add to the buffer + buffer.append(ch); + if (ch == '"') { + inQuote = true; + } + } + } else { + // already got something + // just keep on adding to the buffer + buffer.append(ch); + } + } + } else { + // Value is quoted + // Keep on adding until closing quote is encountered + buffer.append(ch); + if (ch == '"') { + inQuote = false; + } + } + // Have I reached the end of the challenge string? + if (i == len) { + value = buffer.toString().trim(); + gotIt = true; + } + } + if (gotIt) { + // Got something + if ((name == null) || (name.equals(""))) { + throw new MalformedChallengeException("Invalid challenge: " + challengeStr); + } + // Strip quotes when present + if ((value != null) && (value.length() > 1)) { + if ((value.charAt(0) == '"') + && (value.charAt(value.length() - 1) == '"')) { + value = value.substring(1, value.length() - 1); + } + } + + elements.put(name, value); + parsingName = true; + gotIt = false; + } + } + return elements; + } +} Index: java/org/apache/commons/httpclient/AuthScheme.java =================================================================== RCS file: java/org/apache/commons/httpclient/AuthScheme.java diff -N java/org/apache/commons/httpclient/AuthScheme.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/AuthScheme.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,139 @@ +/* + * ==================================================================== + * + * 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 org.apache.commons.httpclient.Credentials; + +/** + *

+ * This interface represents an abstract 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 + *

+ * + * @author Oleg Kalnichevski + * + * @since 2.0beta1 + */ + +public interface AuthScheme { + + /** + * Returns textual designation of the given authentication scheme. + * + * @return the name of the given authentication scheme + */ + public String getSchemeName(); + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return the parameter with the given name + */ + public String getParameter(final String name); + + /** + * Returns authentication realm. If the concept of an authentication + * realm is not applicable to the given authentication scheme, returns + * null. + * + * @return the authentication realm + */ + public String getRealm(); + + /** + * Produces an authorization string for the given set of {@link Credentials}, + * method name and URI using the given authentication scheme. + * + * @param credentials The set of credentials to be used for athentication + * @param method The name of the method that requires authorization. + * This parameter may be ignored, if it is irrelevant + * or not applicable to the given authentication scheme + * @param uri The URI for which authorization is needed. + * This parameter may be ignored, if it is irrelevant or not + * applicable to the given authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return the authorization string + * + * @see org.apache.commons.httpclient.HttpMethod#getName() + * @see org.apache.commons.httpclient.HttpMethod#getPath() + */ + public String authenticate(Credentials credentials, String method, String uri) + throws AuthenticationException; + +} Index: java/org/apache/commons/httpclient/AuthSchemeBase.java =================================================================== RCS file: java/org/apache/commons/httpclient/AuthSchemeBase.java diff -N java/org/apache/commons/httpclient/AuthSchemeBase.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/AuthSchemeBase.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,120 @@ +/* + * ==================================================================== + * + * 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.Map; +import java.util.Hashtable; +import java.util.StringTokenizer; + +/** + *

+ * Abstract authentication scheme class that implements {@link AuthScheme} + * interface and provides a default contstructor. + *

+*/ +public abstract class AuthSchemeBase implements AuthScheme { + + /** + * Original challenge string as received from the server. + */ + private String challenge = null; + + /** + * Constructor for an abstract authetication schemes. + * + * @param challenge authentication challenge + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public AuthSchemeBase(final String challenge) + throws MalformedChallengeException { + super(); + if (challenge == null) { + throw new IllegalArgumentException("Challenge may not be null"); + } + this.challenge = challenge; + } + + /** + * @see java.lang.Object#equals(Object) + */ + public boolean equals(Object obj) { + if (obj instanceof AuthSchemeBase) { + return this.challenge.equals(((AuthSchemeBase)obj).challenge); + } else { + return super.equals(obj); + } + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return this.challenge.hashCode(); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return this.challenge; + } +} Index: java/org/apache/commons/httpclient/AuthenticationException.java =================================================================== RCS file: java/org/apache/commons/httpclient/AuthenticationException.java diff -N java/org/apache/commons/httpclient/AuthenticationException.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/AuthenticationException.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,86 @@ +/* + * ==================================================================== + * + * 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 org.apache.commons.httpclient.HttpException; + +/** + * Signals a failure in authentication process + * + * @author Oleg Kalnichevski + * + * @since 2.0 + */ +public class AuthenticationException extends HttpException { + + /** + * @see HttpException#HttpException() + */ + public AuthenticationException() { + super(); + } + + /** + * @see HttpException#HttpException(String) + */ + public AuthenticationException(String message) { + super(message); + } +} Index: java/org/apache/commons/httpclient/Authenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v retrieving revision 1.39 diff -u -r1.39 Authenticator.java --- java/org/apache/commons/httpclient/Authenticator.java 28 Jan 2003 04:40:20 -0000 1.39 +++ java/org/apache/commons/httpclient/Authenticator.java 21 Mar 2003 10:56:58 -0000 @@ -63,13 +63,9 @@ package org.apache.commons.httpclient; -import org.apache.commons.httpclient.util.Base64; - -import java.security.MessageDigest; -import java.util.Map; -import java.util.Hashtable; -import java.util.StringTokenizer; - +import java.util.ArrayList; +import org.apache.commons.httpclient.auth.HttpAuthenticator; +import org.apache.commons.httpclient.auth.AuthScheme; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -78,14 +74,10 @@ * provides utility methods for generating responses to HTTP www and proxy * authentication challenges. * - *

- * Preemptive authentication can be turned on by using the property value of - * #PREEMPTIVE_PROPERTY. If left unspecified, it has the default value of - * #PREEMPTIVE_DEFAULT. This configurable behaviour conforms to rcf2617: *

* A client SHOULD assume that all paths at or deeper than the depth of the * last symbolic element in the path field of the Request-URI also are within - * the protection space specified by the Basic realm value of the current + * the protection space specified by the BasicScheme realm value of the current * challenge. A client MAY preemptively send the corresponding Authorization * header with requests for resources in that space without receipt of another * challenge from the server. Similarly, when a client sends a request to a @@ -101,7 +93,7 @@ * @author Sean C. Sullivan * @author Adrian Sutton * @author Mike Bowler - * @version $Revision: 1.39 $ $Date: 2003/01/28 04:40:20 $ + * @author Oleg Kalnichevski */ public class Authenticator { @@ -112,20 +104,6 @@ */ private static final Log LOG = LogFactory.getLog(Authenticator.class); - - /** - * The boolean property name to turn on preemptive authentication. - */ - public static final String PREEMPTIVE_PROPERTY = - "httpclient.authentication.preemptive"; - - - /** - * The default property value for #PREEMPTIVE_PROPERTY. - */ - public static final String PREEMPTIVE_DEFAULT = "false"; - - /** * The www authenticate challange header. */ @@ -150,86 +128,9 @@ public static final String PROXY_AUTH_RESP = "Proxy-Authorization"; - /** - * Hexa values used when creating 32 character long digest in HTTP Digest - * in case of authentication. - * - * @see #encode(byte[]) - */ - private static final char[] HEXADECIMAL = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', - 'e', 'f' - }; - // ---------------------------------------------------------------- Methods /** - * Creates an MD5 response digest. - * - * @param uname Username - * @param pwd Password - * @param mapCredentials map containing necessary header parameters to - * construct the digest. It must/can contain: uri, realm, nonce, - * cnonce, qop, nc. - * @return The created digest as string. This will be the response tag's - * value in the Authentication HTTP header. - * @throws HttpException when MD5 is an unsupported algorithm - * TODO: + Add createDigest() method - */ - public static String createDigest(String uname, String pwd, - Map mapCredentials) throws HttpException { - - LOG.trace("enter Authenticator.createDigest(String, String, Map)"); - - final String digAlg = "MD5"; - - // Collecting required tokens - String uri = removeQuotes((String) mapCredentials.get("uri")); - String realm = removeQuotes((String) mapCredentials.get("realm")); - String nonce = removeQuotes((String) mapCredentials.get("nonce")); - String nc = removeQuotes((String) mapCredentials.get("nc")); - String cnonce = removeQuotes((String) mapCredentials.get("cnonce")); - String qop = removeQuotes((String) mapCredentials.get("qop")); - String method = (String) mapCredentials.get("methodname"); - - if (qop != null) { - qop = "auth"; - } - - MessageDigest md5Helper; - - try { - md5Helper = MessageDigest.getInstance(digAlg); - } catch (Exception e) { - LOG.error("ERROR! Unsupported algorithm in HTTP Digest " - + "authentication: " + digAlg, e); - throw new HttpException("Unsupported algorithm in HTTP Digest " - + "authentication: " + digAlg); - } - - // Calculating digest according to rfc 2617 - String a2 = method + ":" + uri; - String md5a2 = encode(md5Helper.digest(HttpConstants.getBytes(a2))); - String digestValue = uname + ":" + realm + ":" + pwd; - String md5a1 - = encode(md5Helper.digest(HttpConstants.getBytes(digestValue))); - String serverDigestValue; - - if (qop == null) { - serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2; - } else { - serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce - + ":" + qop + ":" + md5a2; - } - - String serverDigest = - encode(md5Helper.digest(HttpConstants.getBytes(serverDigestValue))); - - return serverDigest; - } - - - /** * Add requisite authentication credentials to the given method in * the given state if possible. * @@ -246,9 +147,7 @@ LOG.trace("enter Authenticator.authenticate(HttpMethod, HttpState)"); - Header challengeHeader = method.getResponseHeader(WWW_AUTH); - - return authenticate(method, state, challengeHeader, WWW_AUTH_RESP); + return authenticate(method, state, false); } @@ -270,376 +169,18 @@ LOG.trace("enter Authenticator.authenticateProxy(HttpMethod, " + "HttpState)"); - Header challengeHeader = method.getResponseHeader(PROXY_AUTH); - - return authenticate(method, state, challengeHeader, PROXY_AUTH_RESP); - } - - - /** - * Return a Basic Authorization header value for the given {@link - * UsernamePasswordCredentials}. - * - * @param credentials the credentials to encode. Must be non-null - * @return the credentials as a Basic Authentication string - */ - private static String basic(UsernamePasswordCredentials credentials) { - LOG.trace("enter Authenticator.basic(UsernamePasswordCredentials)"); - - final String authString = credentials.getUserName() + ":" - + credentials.getPassword(); - - return "Basic " + HttpConstants.getString( - Base64.encode(HttpConstants.getBytes(authString))); - } - - - /** - * Create a Basic Authorization header for the given realm - * and state to the given method. - * - * @param realm the basic realm to authenticate to - * @param state a {@link HttpState} object providing {@link Credentials} - * @param responseHeader the header's name to store the authentication - * response in. PROXY_AUTH_RESP will force the proxy credentials to - * be used. - * @return a basic Authorization header - * @throws HttpException when no matching credentials are available - */ - private static Header basic(String realm, HttpState state, - String responseHeader) throws HttpException { - - LOG.trace("enter Authenticator.basic(String, HttpState, String)"); - - boolean proxy = PROXY_AUTH_RESP.equals(responseHeader); - UsernamePasswordCredentials credentials = null; - - try { - credentials = (UsernamePasswordCredentials) (proxy - ? state.getProxyCredentials(realm) - : state.getCredentials(realm)); - } catch (ClassCastException e) { - throw new HttpException("UsernamePasswordCredentials required for " - + "Basic authentication."); - } - - if (credentials == null) { - throw new HttpException("No credentials available for the Basic " - + "authentication realm \'" + realm + "\'"); - } - // else - return new Header(responseHeader, Authenticator.basic(credentials)); - } - - - /** - * Create a NTLM Authorization header for the given - * Challenge and state to the given method. - * - * @param challenge The challenge. - * @param method the {@link HttpMethod request} requiring the ntlm - * @param state a {@link HttpState} object providing - * {@link Credentials} - * @param responseHeader the header's name to store the authentication - * response in. PROXY_AUTH_RESP will force the proxy credentials to - * be used. - * @return a ntlm Authorization header - * @throws HttpException when no matching credentials are available - */ - private static Header ntlm(String challenge, HttpMethod method, - HttpState state, String responseHeader) throws HttpException { - - LOG.trace("enter Authenticator.ntlm(String, HttpMethod, HttpState, " - + "String)"); - - boolean proxy = PROXY_AUTH_RESP.equals(responseHeader); - - NTCredentials credentials = null; - - if (method.getRequestHeader("Host") != null) { - String host = method.getRequestHeader("Host").getValue(); - try { - credentials = (NTCredentials) (proxy - ? state.getProxyCredentials(host) - : state.getCredentials(host)); - } catch (ClassCastException e) { - throw new HttpException("NTCredentials required " - + "for NTLM authentication."); - } - } - - if (credentials == null) { - LOG.info("No credentials for specific host, " - + "attempting to use default credentials."); - try { - credentials = (NTCredentials) (proxy - ? state.getProxyCredentials(null) - : state.getCredentials(null)); - } catch (ClassCastException e) { - throw new HttpException( - "NTCredentials required for NTLM authentication."); - } - } - - try { - challenge = - challenge.substring(challenge.toLowerCase().indexOf("ntlm") - + "ntlm".length()).trim(); - } catch (IndexOutOfBoundsException e) { - throw new HttpException("Invalid NTLM challenge."); - } - - if (credentials == null) { - throw new HttpException("No credentials available for NTLM " - + "authentication."); - } else { - NTLM ntlm = new NTLM(); - String response = "NTLM " + ntlm.getResponseFor(challenge, - credentials.getUserName(), credentials.getPassword(), - credentials.getHost(), credentials.getDomain()); - if (LOG.isDebugEnabled()) { - LOG.debug("Replying to challenge with: " + response); - } - return new Header(responseHeader, response); - } - } - - - /** - * Create a Digest Authorization header for the given realm - * and state to the given method. - * - * @param realm the basic realm to authenticate to - * @param method the {@link HttpMethod request} requiring the digest - * @param state a {@link HttpState} object providing {@link Credentials} - * @param responseHeader the header's name to store the authentication - * response in. PROXY_AUTH_RESP will force the proxy credentials to - * be used. - * @return a digest Authorization header - * @throws HttpException when no matching credentials are available - */ - private static Header digest(String realm, HttpMethod method, - HttpState state, String responseHeader) throws HttpException { - - LOG.trace("enter Authenticator.digest(String, HttpMethod, HttpState, " - + "String)"); - - boolean proxy = PROXY_AUTH_RESP.equals(responseHeader); - UsernamePasswordCredentials credentials = null; - - try { - credentials = (UsernamePasswordCredentials) (proxy - ? state.getProxyCredentials(realm) - : state.getCredentials(realm)); - } catch (ClassCastException e) { - throw new HttpException("UsernamePasswordCredentials required for " - + "Digest authentication."); - } - - if (credentials == null) { - if (LOG.isInfoEnabled()) { - LOG.info("No credentials found for realm \"" + realm + "\", " - + "attempting to use default credentials."); - } - - try { - credentials = (UsernamePasswordCredentials) (proxy - ? state.getProxyCredentials(null) - : state.getCredentials(null)); - } catch (ClassCastException e) { - throw new HttpException("UsernamePasswordCredentials required " - + "for Digest authentication."); - } - } - - if (credentials == null) { - throw new HttpException("No credentials available for the Digest " - + "authentication realm \"" + realm + "\"/"); - } else { - Map headers = getHTTPDigestCredentials(method, proxy); - headers.put("cnonce", "\"" + createCnonce() + "\""); - headers.put("nc", "00000001"); - headers.put("uri", method.getPath()); - headers.put("methodname", method.getName()); - - return new Header(responseHeader, Authenticator.digest(credentials, - headers)); - } - } - - - /** - * Return a Digest Authorization header value for the given {@link - * UsernamePasswordCredentials}. - * - * @param credentials Credentials to create the digest with - * @param mapHeaders The headers for the current request - * @return a string containing the authorization header for digest - * @throws HttpException When a recoverable error occurs - */ - private static String digest(UsernamePasswordCredentials credentials, - Map mapHeaders) throws HttpException { - - LOG.trace("enter Authenticator.digest(UsernamePasswordCredentials, " - + "Map)"); - - String digest = createDigest(credentials.getUserName(), - credentials.getPassword(), mapHeaders); - - return "Digest " + createDigestHeader(credentials.getUserName(), - mapHeaders, digest); - } - - - /** - * Processes the authenticate HTTP header received from the server that - * requires Digest authentication. - * - * @param method The HTTP method. - * @param proxy true if authorizing for a proxy - * - * @return The parameters from the authenticate header as a Map or - * an empty Map if there is no valid authorization. - * - * @since 2.0 - * @see #processDigestToken(String, java.util.Map) - * @see PROXY_AUTH - * @see WWW_AUTH - * - */ - private static Map getHTTPDigestCredentials(HttpMethod method, - boolean proxy) { - LOG.trace("enter Authenticator.getHTTPDigestCredentials(HttpMethod, " - + "boolean)"); - - //Determine wether to use proxy or www header - String authName = proxy ? PROXY_AUTH : WWW_AUTH; - String authHeader = null; - - //Get the authorization header value - try { - authHeader = method.getResponseHeader(authName).getValue(); - authHeader = authHeader.substring(7).trim(); - } catch (NullPointerException npe) { - return (Map) (new java.util.Hashtable(0)); - } - - // map of digest tokens - Map mapTokens = new Hashtable(17); - - //parse the authenticate header - int i = 0; - int j = authHeader.indexOf(","); - - while (j >= 0) { - processDigestToken(authHeader.substring(i, j), mapTokens); - i = j + 1; - j = authHeader.indexOf(",", i); - } - - if (i < authHeader.length()) { - processDigestToken(authHeader.substring(i), mapTokens); - } - - return mapTokens; - } - - /** - * Parses an authenticate header into a map of authentication challenges - * keyed on the lowercase authentication scheme. - * - * @param authHeader the authentication header - * @return a map of authentication challenges or an empty map if the - * authHeader is null or contains a null value - * @since 2.0 - */ - private static Map parseAuthenticateHeader(Header authHeader) { - - LOG.trace("enter parseAuthenticateHeader(Header)"); - - if (LOG.isDebugEnabled()) { - LOG.debug("Attempting to parse authenticate header: '" - + authHeader + "'"); - } - if (authHeader == null || authHeader.getValue() == null) { - return new Hashtable(0); - } - - String authValue = authHeader.getValue().trim(); - Map challengeMap = new Hashtable(7); - - final int authValueLength = authValue.length(); - int atStart = authValueLength > 0 ? 0 : -1; // start position - int atQuote1 = 0; // position of quote 1 - int atQuote2 = 0; // position of quote 2 - int atComma; // position of comma - int atSpace; // position of blank - - String challenge = null; // an authentication challenge - String scheme = null; // the scheme from the challenge - - try { - while (atStart >= 0 && atStart < authValueLength) { - atQuote1 = authValue.indexOf('"', atStart); - atQuote2 = authValue.indexOf('"', atQuote1 + 1); - atComma = authValue.indexOf(',', atStart); - - // skip any commas in quotes - while (atComma > atQuote1 && atComma < atQuote2 - && atComma > 0) { - atComma = authValue.indexOf(',', atComma + 1); - } - - // set atComma to be the end if there is no comma - if (atComma < 0) { - atComma = authValueLength; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("atStart =" + atStart + ", atQuote1 =" - + atQuote1 + ", atQuote2=" + atQuote2 + ", atComma =" - + atComma); - } - - try { - //pull the current challenge and advance the start - challenge = authValue.substring(atStart, atComma).trim(); - atStart = atComma + 1; - - //find the blank and parse out the scheme - atSpace = challenge.indexOf(' '); - scheme = (atSpace > 0) - ? challenge.substring(0, atSpace).trim() : challenge; - - //store the challenge keyed on the scheme - challengeMap.put(scheme.toLowerCase(), challenge); - if (LOG.isDebugEnabled()) { - LOG.debug(scheme.toLowerCase() + "=>" + challenge); - } - - } catch (StringIndexOutOfBoundsException e) { - LOG.warn("Parsing authorization challenge'" + challenge - + "' failed", e); - } - } // end of while - - } catch (StringIndexOutOfBoundsException e) { - LOG.warn("Parsing authorization header value'" + authValue - + "' failed", e); - } - - return challengeMap; + return authenticate(method, state, true); } /** * Add requisite authentication credentials to the given method - * using the given the challengeHeader. Currently Basic and - * Digest authentication are supported. If the challengeHeader is + * using the given the challengeHeader. Currently BasicScheme and + * DigestScheme authentication are supported. If the challengeHeader is * null, the default authentication credentials will be sent. * * @param method the http method to add the authentication header to - * @param authenticateHeader the header the web server created to challenge + * @param authenticateHeader the array of headers the web server created to challenge * the credentials * @param state the http state object providing {@link Credentials} * @param responseHeader the response header to add (e.g. proxy or standard) @@ -652,274 +193,54 @@ * @see HttpMethod#addRequestHeader */ private static boolean authenticate(HttpMethod method, HttpState state, - Header authenticateHeader, String responseHeader) + boolean proxy) throws HttpException, UnsupportedOperationException { LOG.trace("enter Authenticator.authenticate(HttpMethod, HttpState, " + "Header, String)"); - // check the preemptive policy - // TODO: this needs to be a service from some configuration class - String preemptiveDefault = - System.getProperties().getProperty(PREEMPTIVE_PROPERTY, - PREEMPTIVE_DEFAULT); - preemptiveDefault = preemptiveDefault.trim().toLowerCase(); - - if (!(preemptiveDefault.equals("true") - || preemptiveDefault.equals("false"))) { // property problem - LOG.warn("Configuration property " + PREEMPTIVE_PROPERTY - + " must be either true or false. Using default: " - + PREEMPTIVE_DEFAULT); - preemptiveDefault = PREEMPTIVE_DEFAULT; - } + String challengeheader = proxy ? PROXY_AUTH : WWW_AUTH; - boolean preemptive = ("true".equals(preemptiveDefault)); + // I REALLY hate doing this, but I need to avoid multiple autorization + // headers being condenced itno one. Currently HttpMethod interface + // does not provide this kind of functionality + Header[] headers = method.getResponseHeaders(); + ArrayList headerlist = new ArrayList(); + for (int i = 0; i < headers.length; i++ ) { + Header header = headers[i]; + if (header.getName().equalsIgnoreCase(challengeheader)) { + headerlist.add(header); + } + } + headers = (Header [])headerlist.toArray(new Header[headerlist.size()]); + for (int i = 0; i < headers.length; i++ ) { + headers[i] = (Header)headerlist.get(i); + } + headerlist = null; //if there is no challenge, attempt to use preemptive authorization - if (authenticateHeader == null) { - if (preemptive) { + if (headers.length == 0) { + if (state.isPreemptiveAuthentication()) { LOG.debug("Preemptively sending default basic credentials"); - - try { - Header requestHeader = Authenticator.basic(null, state, - responseHeader); - method.addRequestHeader(requestHeader); - return true; - } catch (HttpException httpe) { - if (LOG.isDebugEnabled()) { - LOG.debug( - "No default credentials to preemptively send"); - } - return false; + if (proxy) { + return HttpAuthenticator.authenticateProxyDefault(method, state); + } else { + return HttpAuthenticator.authenticateDefault(method, state); } } - // else { // no challenge and no default credentials so do nothing return false; } - // parse the authenticate header - Map challengeMap = parseAuthenticateHeader(authenticateHeader); - - // determine the most secure request header to add - Header requestHeader = null; - if (challengeMap.containsKey("ntlm")) { - String challenge = (String) challengeMap.get("ntlm"); - requestHeader = Authenticator.ntlm(challenge, method, state, - responseHeader); - } else if (challengeMap.containsKey("digest")) { - String challenge = (String) challengeMap.get("digest"); - String realm = parseRealmFromChallenge(challenge); - requestHeader = Authenticator.digest(realm, method, state, - responseHeader); - } else if (challengeMap.containsKey("basic")) { - String challenge = (String) challengeMap.get("basic"); - String realm = parseRealmFromChallenge(challenge); - requestHeader = Authenticator.basic(realm, state, responseHeader); - } else if (challengeMap.size() == 0) { - throw new HttpException("No authentication scheme found in '" - + authenticateHeader + "'"); - } else { - throw new UnsupportedOperationException( - "Requested authentication scheme " + challengeMap.keySet() - + " is unsupported"); - } - - // Add the header if it has been created and return true - if (requestHeader != null) { // add the header - method.addRequestHeader(requestHeader); - return true; - } - // else { // don't add the header - return false; - } - - - /** - * Creates a random cnonce value based on the current time. - * - * @return The cnonce value as String. - * @throws HttpException if MD5 algorithm is not supported. - * TODO: + Add createCnonce() method - */ - private static String createCnonce() throws HttpException { - LOG.trace("enter Authenticator.createCnonce()"); - - String cnonce; - final String digAlg = "MD5"; - MessageDigest md5Helper; - - try { - md5Helper = MessageDigest.getInstance(digAlg); - } catch (Exception e) { - LOG.error("ERROR! Unsupported algorithm in HTTP Digest " - + "authentication: " + digAlg); - throw new HttpException("Unsupported algorithm in HTTP Digest " - + "authentication: " + digAlg); - } - - cnonce = Long.toString(System.currentTimeMillis()); - cnonce = encode(md5Helper.digest(HttpConstants.getBytes(cnonce))); - - return cnonce; - } - - - /** - * Creates the header information that must be specified after the "Digest" - * string in the HTTP Authorization header (digest-response in RFC2617). - * - * @param uname Username - * @param mapCredentials Map containing header information (uri, realm, - * nonce, nc, cnonce, opaque, qop). - * @param digest The response tag's value as String. - * @return The digest-response as String. - * TODO: + Add createDigestHeader() method - */ - private static String createDigestHeader(String uname, Map mapCredentials, - String digest) { - - LOG.trace("enter Authenticator.createDigestHeader(String, Map, " - + "String)"); - - StringBuffer sb = new StringBuffer(); - String uri = removeQuotes((String) mapCredentials.get("uri")); - String realm = removeQuotes((String) mapCredentials.get("realm")); - String nonce = removeQuotes((String) mapCredentials.get("nonce")); - String nc = removeQuotes((String) mapCredentials.get("nc")); - String cnonce = removeQuotes((String) mapCredentials.get("cnonce")); - String opaque = removeQuotes((String) mapCredentials.get("opaque")); - String response = digest; - String qop = removeQuotes((String) mapCredentials.get("qop")); - - if (qop != null) { - qop = "auth"; //we only support auth - } - - String algorithm = "MD5"; //we only support MD5 - - sb.append("username=\"" + uname + "\"") - .append(", realm=\"" + realm + "\"") - .append(", nonce=\"" + nonce + "\"").append(", uri=\"" + uri + "\"") - .append(((qop == null) ? "" : ", qop=\"" + qop + "\"")) - .append(", algorithm=\"" + algorithm + "\"") - .append(((qop == null) ? "" : ", nc=" + nc)) - .append(((qop == null) ? "" : ", cnonce=\"" + cnonce + "\"")) - .append(", response=\"" + response + "\"") - .append((opaque == null) ? "" : ", opaque=\"" + opaque + "\""); - - return sb.toString(); - } - - - /** - * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long - * String according to RFC 2617. - * - * @param binaryData array containing the digest - * @return encoded MD5, or null if encoding failed - * TODO: + Add encode() method - */ - private static String encode(byte[] binaryData) { - LOG.trace("enter Authenticator.encode(byte[])"); - - if (binaryData.length != 16) { - return null; - } - - char[] buffer = new char[32]; - for (int i = 0; i < 16; i++) { - int low = (int) (binaryData[i] & 0x0f); - int high = (int) ((binaryData[i] & 0xf0) >> 4); - buffer[i * 2] = HEXADECIMAL[high]; - buffer[(i * 2) + 1] = HEXADECIMAL[low]; - } - - return new String(buffer); - } - - - /** - * Parse the realm from the authentication challenge - * - * @param challenge the authentication challenge - * @return the realm - * @throws HttpException when the realm can't be parsed - */ - private static String parseRealmFromChallenge(String challenge) - throws HttpException { - - // FIXME: Note that this won't work if there is more than one realm - // within the challenge - try { - StringTokenizer strtok = new StringTokenizer(challenge, "="); - String realmName = strtok.nextToken().trim(); - String realm = strtok.nextToken().trim(); - int atFirst = realm.indexOf('"'); - int atLast = realm.lastIndexOf('"'); - - if ((atFirst + 1) < atLast) { - realm = realm.substring(atFirst + 1, atLast); - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Parsed realm '" + realm + "' from challenge '" - + challenge + "'"); - } - - return realm; - } catch (Exception ex) { - throw new HttpException("Failed to parse realm from challenge '" - + challenge + "'"); + // parse the authenticate headers + AuthScheme authscheme = HttpAuthenticator.selectAuthScheme(headers); + if (LOG.isDebugEnabled()) { + LOG.debug("Using " + authscheme.getSchemeName() + " authentication scheme"); } - } - - - /** - * Takes an entry of "xxx=yyy" format, partitions into a key - * and a value (key will be the left side, value will be the right side of - * the equal sign) and places that into a Map. - * - * @param token the entry to be processed - * @param tokens the java.util.Map into which the processed - * entry is placed (only if it has "xxx=yyy" format). - * TODO: + Add processDigestToken() method - */ - private static void processDigestToken(String token, Map tokens) { - LOG.trace("enter Authenticator.processDigestToken(String, Map)"); - - int atEqual = token.indexOf("="); - - if ((atEqual > 0) && (atEqual < (token.length() - 1))) { - tokens.put(token.substring(0, atEqual).trim(), - token.substring(atEqual + 1).trim()); + if (proxy) { + return HttpAuthenticator.authenticateProxy(authscheme, method, state); + } else { + return HttpAuthenticator.authenticate(authscheme, method, state); } - } - - - /** - * Takes a String and cuts its prefix until the first double - * quotation mark and its suffix from the last double quotation mark (and - * cuts also the quotation marks). - * - * @param str the String from which the prefix and suffix is - * to be cut. - * @return the stumped String if the format of - * str is ""; Otherwise the return value - * is same as str - */ - private static String removeQuotes(String str) { - LOG.trace("enter Authenticator.removeQuotes(String)"); - - if (str == null) { - return null; - } - int atFirst = str.indexOf("\"") + 1; - int atLast = str.lastIndexOf("\""); - - return (atFirst > 0 && atLast > atFirst) - ? str.substring(atFirst, atLast) : str; } - -} - +} \ No newline at end of file Index: java/org/apache/commons/httpclient/BasicScheme.java =================================================================== RCS file: java/org/apache/commons/httpclient/BasicScheme.java diff -N java/org/apache/commons/httpclient/BasicScheme.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/BasicScheme.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,162 @@ +/* + * ==================================================================== + * + * 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 org.apache.commons.httpclient.HttpConstants; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.util.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * Basic authentication scheme as defined in RFC 2617. + *

+ * + * @author Remy Maucherat + * @author Rodney Waldhoff + * @author Jeff Dever + * @author Ortwin Gl� + * @author Sean C. Sullivan + * @author Adrian Sutton + * @author Mike Bowler + * @author Oleg Kalnichevski + */ + +public class BasicScheme extends RFC2617Scheme +{ + /** Log object for this class. */ + private static final Log LOG = LogFactory.getLog(BasicScheme.class); + + /** + * Constructor for the basic authetication scheme. + * + * @param challenge authentication challenge + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public BasicScheme(final String challenge) throws MalformedChallengeException { + super(challenge); + } + + + /** + * Returns textual designation of the basic authentication scheme. + * + * @return Basic + */ + public String getSchemeName() { + return "Basic"; + } + + /** + * Produces basic authorization string for the given set of + * {@link Credentials}. + * + * @param credentials The set of credentials to be used for athentication + * @param method Method name is ignored by the basic authentication scheme + * @param uri URI is ignored by the basic authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a basic authorization string + */ + public String authenticate(Credentials credentials, String method, String uri) + throws AuthenticationException { + + LOG.trace("enter BasicScheme.authenticate(Credentials, String, String)"); + + UsernamePasswordCredentials usernamepassword = null; + try { + usernamepassword = (UsernamePasswordCredentials)credentials; + } catch(ClassCastException e) { + throw new AuthenticationException( + "Credentials cannot be used for basic authentication: " + + credentials.toString()); + } + return BasicScheme.authenticate(usernamepassword); + } + + /** + * Return a basic Authorization header value for the given + * {@link UsernamePasswordCredentials}. + * + * @param credentials The credentials to encode. + * + * @return a basic authorization string + */ + public static String authenticate(UsernamePasswordCredentials credentials) { + + LOG.trace("enter BasicScheme.authenticate(UsernamePasswordCredentials)"); + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + StringBuffer buffer = new StringBuffer(); + buffer.append(credentials.getUserName()); + buffer.append(":"); + buffer.append(credentials.getPassword()); + + return "Basic " + HttpConstants.getAsciiString( + Base64.encode(HttpConstants.getBytes(buffer.toString()))); + } +} 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.9 diff -u -r1.9 ConnectMethod.java --- java/org/apache/commons/httpclient/ConnectMethod.java 16 Mar 2003 00:09:17 -0000 1.9 +++ java/org/apache/commons/httpclient/ConnectMethod.java 21 Mar 2003 10:56:58 -0000 @@ -64,6 +64,8 @@ package org.apache.commons.httpclient; import java.io.IOException; + +import org.apache.commons.httpclient.auth.HttpAuthenticator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -143,9 +145,9 @@ if (Wire.enabled()) { Wire.output(line); } - Header header = method.getRequestHeader(Authenticator.PROXY_AUTH_RESP); + Header header = method.getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); if (header == null) { - header = getRequestHeader(Authenticator.PROXY_AUTH_RESP); + header = getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); } if (header != null) { line = header.toExternalForm(); Index: java/org/apache/commons/httpclient/DigestScheme.java =================================================================== RCS file: java/org/apache/commons/httpclient/DigestScheme.java diff -N java/org/apache/commons/httpclient/DigestScheme.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/DigestScheme.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,353 @@ +/* + * ==================================================================== + * + * 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.Map; +import java.security.MessageDigest; + +import org.apache.commons.httpclient.HttpConstants; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * Digest authentication scheme as defined in RFC 2617. + *

+ * + * @author Remy Maucherat + * @author Rodney Waldhoff + * @author Jeff Dever + * @author Ortwin Gl� + * @author Sean C. Sullivan + * @author Adrian Sutton + * @author Mike Bowler + * @author Oleg Kalnichevski + */ + +public class DigestScheme extends RFC2617Scheme +{ + /** Log object for this class. */ + private static final Log LOG = LogFactory.getLog(DigestScheme.class); + + /** + * Hexa values used when creating 32 character long digest in HTTP DigestScheme + * in case of authentication. + * + * @see #encode(byte[]) + */ + private static final char[] HEXADECIMAL = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' + }; + + /** + * Constructor for the digest authentication scheme. + * + * @param challenge The authentication challenge + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public DigestScheme(final String challenge) + throws MalformedChallengeException { + super(challenge); + this.getParameters().put("nc", "00000001"); + } + + + /** + * Returns textual designation of the digest authentication scheme. + * + * @return Digest + */ + public String getSchemeName() { + return "Digest"; + } + + /** + * Produces a digest authorization string for the given set of + * {@link Credentials}, method name and URI. + * + * @param credentials A set of credentials to be used for athentication + * @param method the name of the method that requires authorization. + * @param uri The URI for which authorization is needed. + * + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a digest authorization string + * + * @see org.apache.commons.httpclient.HttpMethod#getName() + * @see org.apache.commons.httpclient.HttpMethod#getPath() + */ + public String authenticate(Credentials credentials, String method, String uri) + throws AuthenticationException { + + LOG.trace("enter DigestScheme.authenticate(Credentials, String, String)"); + + UsernamePasswordCredentials usernamepassword = null; + try { + usernamepassword = (UsernamePasswordCredentials)credentials; + } catch(ClassCastException e ) { + throw new AuthenticationException( + "Credentials cannot be used for basic authentication: " + + credentials.toString()); + } + this.getParameters().put("cnonce", createCnonce()); + this.getParameters().put("methodname", method); + this.getParameters().put("uri", uri); + return DigestScheme.authenticate(usernamepassword, getParameters()); + } + + /** + * Produces a digest authorization string for the given set of + * {@link UsernamePasswordCredentials} and authetication parameters. + * + * @param credentials Credentials to create the digest with + * @param params The authetication parameters. The following + * parameters are expected: uri, realm, + * nonce, nc, cnonce, + * qop, methodname. + * + * @return a digest authorization string + * + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + */ + public static String authenticate(UsernamePasswordCredentials credentials, + Map params) throws AuthenticationException { + + LOG.trace("enter DigestScheme.authenticate(UsernamePasswordCredentials, Map)"); + + String digest = createDigest(credentials.getUserName(), + credentials.getPassword(), params); + + return "Digest " + createDigestHeader(credentials.getUserName(), + params, digest); + } + + /** + * Creates an MD5 response digest. + * + * @param uname Username + * @param pwd Password + * @param params The parameters necessary to construct the digest. + * The following parameters are expected: uri, + * realm, nonce, nc, + * cnonce, qop, methodname. + * + * @return The created digest as string. This will be the response tag's + * value in the Authentication HTTP header. + * @throws AuthenticationException when MD5 is an unsupported algorithm + */ + public static String createDigest(String uname, String pwd, + Map params) throws AuthenticationException { + + LOG.trace("enter DigestScheme.createDigest(String, String, Map)"); + + final String digAlg = "MD5"; + + // Collecting required tokens + String uri = (String) params.get("uri"); + String realm = (String) params.get("realm"); + String nonce = (String) params.get("nonce"); + String nc = (String) params.get("nc"); + String cnonce = (String) params.get("cnonce"); + String qop = (String) params.get("qop"); + String method = (String) params.get("methodname"); + + if (qop != null) { + qop = "auth"; + } + + MessageDigest md5Helper; + + try { + md5Helper = MessageDigest.getInstance(digAlg); + } catch (Exception e) { + throw new AuthenticationException( + "Unsupported algorithm in HTTP Digest authentication: " + + digAlg); + } + + // Calculating digest according to rfc 2617 + String a2 = method + ":" + uri; + String md5a2 = encode(md5Helper.digest(HttpConstants.getBytes(a2))); + String digestValue = uname + ":" + realm + ":" + pwd; + String md5a1 + = encode(md5Helper.digest(HttpConstants.getBytes(digestValue))); + String serverDigestValue; + + if (qop == null) { + serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2; + } else { + serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce + + ":" + qop + ":" + md5a2; + } + + String serverDigest = + encode(md5Helper.digest(HttpConstants.getBytes(serverDigestValue))); + + return serverDigest; + } + + /** + * Creates digest-response header as defined in RFC2617. + * + * @param uname Username + * @param params The parameters necessary to construct the digest header. + * The following parameters are expected: uri, + * realm, nonce, nc, + * cnonce, qop, methodname. + * @param digest The response tag's value as String. + * + * @return The digest-response as String. + */ + public static String createDigestHeader(String uname, Map params, + String digest) { + + LOG.trace("enter DigestScheme.createDigestHeader(String, Map, " + + "String)"); + + StringBuffer sb = new StringBuffer(); + String uri = (String) params.get("uri"); + String realm = (String) params.get("realm"); + String nonce = (String) params.get("nonce"); + String nc = (String) params.get("nc"); + String cnonce = (String) params.get("cnonce"); + String opaque = (String) params.get("opaque"); + String response = digest; + String qop = (String) params.get("qop"); + + if (qop != null) { + qop = "auth"; //we only support auth + } + + String algorithm = "MD5"; //we only support MD5 + + sb.append("username=\"" + uname + "\"") + .append(", realm=\"" + realm + "\"") + .append(", nonce=\"" + nonce + "\"").append(", uri=\"" + uri + "\"") + .append(((qop == null) ? "" : ", qop=\"" + qop + "\"")) + .append(", algorithm=\"" + algorithm + "\"") + .append(((qop == null) ? "" : ", nc=" + nc)) + .append(((qop == null) ? "" : ", cnonce=\"" + cnonce + "\"")) + .append(", response=\"" + response + "\"") + .append((opaque == null) ? "" : ", opaque=\"" + opaque + "\""); + + return sb.toString(); + } + + + /** + * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long + * String according to RFC 2617. + * + * @param binaryData array containing the digest + * @return encoded MD5, or null if encoding failed + */ + private static String encode(byte[] binaryData) { + LOG.trace("enter DigestScheme.encode(byte[])"); + + if (binaryData.length != 16) { + return null; + } + + char[] buffer = new char[32]; + for (int i = 0; i < 16; i++) { + int low = (int) (binaryData[i] & 0x0f); + int high = (int) ((binaryData[i] & 0xf0) >> 4); + buffer[i * 2] = HEXADECIMAL[high]; + buffer[(i * 2) + 1] = HEXADECIMAL[low]; + } + + return new String(buffer); + } + + + /** + * Creates a random cnonce value based on the current time. + * + * @return The cnonce value as String. + * @throws AuthenticationException if MD5 algorithm is not supported. + */ + public static String createCnonce() throws AuthenticationException { + LOG.trace("enter DigestScheme.createCnonce()"); + + String cnonce; + final String digAlg = "MD5"; + MessageDigest md5Helper; + + try { + md5Helper = MessageDigest.getInstance(digAlg); + } catch (Exception e) { + throw new AuthenticationException( + "Unsupported algorithm in HTTP Digest authentication: " + + digAlg); + } + + cnonce = Long.toString(System.currentTimeMillis()); + cnonce = encode(md5Helper.digest(HttpConstants.getBytes(cnonce))); + + return cnonce; + } +} Index: java/org/apache/commons/httpclient/HttpAuthenticator.java =================================================================== RCS file: java/org/apache/commons/httpclient/HttpAuthenticator.java diff -N java/org/apache/commons/httpclient/HttpAuthenticator.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/HttpAuthenticator.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,337 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-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.Map; +import java.util.HashMap; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpState; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Utility methods for HTTP authorization and authentication. This class + * provides utility methods for generating responses to HTTP www and proxy + * authentication challenges. + * + *
+ * A client SHOULD assume that all paths at or deeper than the depth of the + * last symbolic element in the path field of the Request-URI also are within + * the protection space specified by the basic realm value of the current + * challenge. A client MAY preemptively send the corresponding Authorization + * header with requests for resources in that space without receipt of another + * challenge from the server. Similarly, when a client sends a request to a + * proxy, it may reuse a userid and password in the Proxy-Authorization header + * field without receiving another challenge from the proxy server. + *
+ *

+ * + * @author Remy Maucherat + * @author Rodney Waldhoff + * @author Jeff Dever + * @author Ortwin Gl� + * @author Sean C. Sullivan + * @author Adrian Sutton + * @author Mike Bowler + * @author Oleg Kalnichevski + */ +public final class HttpAuthenticator { + + /** Log object for this class. */ + private static final Log LOG = LogFactory.getLog(HttpAuthenticator.class); + + /** + * The www authenticate challange header. + */ + public static final String WWW_AUTH = "WWW-Authenticate"; + + + /** + * The www authenticate response header. + */ + public static final String WWW_AUTH_RESP = "Authorization"; + + + /** + * The proxy authenticate challange header. + */ + public static final String PROXY_AUTH = "Proxy-Authenticate"; + + + /** + * The proxy authenticate response header. + */ + public static final String PROXY_AUTH_RESP = "Proxy-Authorization"; + + /** Chooses the strongest authentication scheme supported from the + * array of authentication challenges. Currently only NTLM, + * Digest, Basic schemes are recognized. + * The NTLM scheme is considered the strongest and is + * preferred to all others. The Digest scheme is preferred to + * the Basic one which provides no encryption for credentials. + * The Basic scheme is used only if it is the only one + * supported. + * + * @param challenges The array of authentication challenges + * + * @return The strongest authentication scheme supported + * + * @throws MalformedChallengeException is thrown if an authentication + * challenge is malformed + * @throws UnsupportedOperationException when none of challenge types + * available is supported. + */ + public static AuthScheme selectAuthScheme(final Header[] challenges) + throws MalformedChallengeException { + LOG.trace("enter HttpAuthenticator.selectAuthScheme(Header[])"); + if (challenges == null) { + throw new IllegalArgumentException("Array of challenges may not be null"); + } + if (challenges.length == 0) { + throw new IllegalArgumentException("Array of challenges may not be empty"); + } + String challenge = null; + Map challengemap = new HashMap(challenges.length); + for (int i = 0; i < challenges.length; i++) { + challenge = challenges[i].getValue(); + String s = AuthChallengeParser.extractScheme(challenge); + challengemap.put(s, challenge); + } + challenge = (String)challengemap.get("ntlm"); + if (challenge != null) { + return new NTLMScheme(challenge); + } + challenge = (String)challengemap.get("digest"); + if (challenge != null) { + return new DigestScheme(challenge); + } + challenge = (String)challengemap.get("basic"); + if (challenge != null) { + return new BasicScheme(challenge); + } + throw new UnsupportedOperationException( + "Authentication scheme(s) not supported: " + challengemap.toString()); + } + + + private static boolean doAuthenticateDefault(HttpMethod method, HttpState state, boolean proxy) + throws AuthenticationException { + if (method == null) { + throw new IllegalArgumentException("HTTP method may not be null"); + } + if (state == null) { + throw new IllegalArgumentException("HTTP state may not be null"); + } + Credentials credentials = proxy ? + state.getProxyCredentials(null) : state.getCredentials(null); + if (credentials == null) { + return false; + } + if (!(credentials instanceof UsernamePasswordCredentials)) { + throw new AuthenticationException( + "Credentials cannot be used for basic authentication: " + + credentials.toString()); + } + String auth = BasicScheme.authenticate((UsernamePasswordCredentials)credentials); + if (auth != null) { + String s = proxy ? PROXY_AUTH_RESP : WWW_AUTH_RESP; + method.addRequestHeader(s, auth); + return true; + } else { + return false; + } + } + + + /** + * Attempt to provide default authentication credentials + * to the given method in the given state using + * basic authentication scheme. + * + * @param method the HttpMethod which requires authentication + * @param state the HttpState object providing Credentials + * + * @return true if the Authenticate response header + * was added + * + * @throws AuthenticationException when a parsing or other error occurs + + * @see HttpState#setCredentials(String,Credentials) + */ + public static boolean authenticateDefault(HttpMethod method, HttpState state) + throws AuthenticationException { + LOG.trace("enter HttpAuthenticator.authenticateDefault(HttpMethod, HttpState)"); + return doAuthenticateDefault(method, state, false); + } + + + /** + * Attempt to provide default proxy authentication credentials + * to the given method in the given state using + * basic authentication scheme. + * + * @param method the HttpMethod which requires authentication + * @param state the HttpState object providing Credentials + * + * @return true if the Proxy-Authenticate response header + * was added + * + * @throws AuthenticationException when a parsing or other error occurs + + * @see HttpState#setCredentials(String,Credentials) + */ + public static boolean authenticateProxyDefault(HttpMethod method, HttpState state) + throws AuthenticationException { + LOG.trace("enter HttpAuthenticator.authenticateProxyDefault(HttpMethod, HttpState)"); + return doAuthenticateDefault(method, state, true); + } + + + private static boolean doAuthenticate(AuthScheme authscheme, HttpMethod method, HttpState state, + boolean proxy) + throws AuthenticationException { + if (authscheme == null) { + throw new IllegalArgumentException("Authentication scheme may not be null"); + } + if (method == null) { + throw new IllegalArgumentException("HTTP method may not be null"); + } + if (state == null) { + throw new IllegalArgumentException("HTTP state may not be null"); + } + String realm = authscheme.getRealm(); + if (realm == null) { + Header hostheader = method.getRequestHeader("host"); + if (hostheader != null) { + realm = hostheader.getValue(); + } + } + if (LOG.isDebugEnabled()) { + if (realm == null) { + LOG.debug("Using default authentication realm"); + } else { + LOG.debug("Using '" + realm + "' authentication realm"); + } + } + Credentials credentials = proxy ? + state.getProxyCredentials(realm) : state.getCredentials(realm); + if (credentials == null) { + throw new AuthenticationException( + "No credentials available for the " + authscheme.getSchemeName() + + "authentication realm '" + realm + "'"); + } + String auth = authscheme.authenticate(credentials, method.getName(), method.getPath()); + if (auth != null) { + String s = proxy ? PROXY_AUTH_RESP : WWW_AUTH_RESP; + method.addRequestHeader(s, auth); + return true; + } else { + return false; + } + } + + + /** + * Attempt to provide requisite authentication credentials to the + * given method in the given state using the given + * authentication scheme. + * + * @param authscheme The authentication scheme to be used + * @param method The HttpMethod which requires authentication + * @param state The HttpState object providing Credentials + * + * @return true if the Authenticate response header was added + * + * @throws AuthenticationException when a parsing or other error occurs + + * @see HttpState#setCredentials(String,Credentials) + */ + public static boolean authenticate(AuthScheme authscheme, HttpMethod method, HttpState state) + throws AuthenticationException { + LOG.trace("enter HttpAuthenticator.authenticate(AuthScheme, HttpMethod, HttpState)"); + return doAuthenticate(authscheme, method, state, false); + } + + + /** + * Attempt to provide requisite proxy authentication credentials + * to the given method in the given state using + * the given authentication scheme. + * + * @param authscheme The authentication scheme to be used + * @param method the HttpMethod which requires authentication + * @param state the HttpState object providing Credentials + * + * @return true if the Proxy-Authenticate response header + * was added + * + * @throws AuthenticationException when a parsing or other error occurs + + * @see HttpState#setCredentials(String,Credentials) + */ + public static boolean authenticateProxy(AuthScheme authscheme, HttpMethod method, HttpState state) + throws AuthenticationException { + LOG.trace("enter HttpAuthenticator.authenticateProxy(AuthScheme, HttpMethod, HttpState)"); + return doAuthenticate(authscheme, method, state, true); + } +} \ No newline at end of file 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.123 diff -u -r1.123 HttpMethodBase.java --- java/org/apache/commons/httpclient/HttpMethodBase.java 13 Mar 2003 17:51:28 -0000 1.123 +++ java/org/apache/commons/httpclient/HttpMethodBase.java 21 Mar 2003 10:56:58 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.123 2003/03/13 17:51:28 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.123 2003/03/13 17:51:28 olegk Exp $ * $Revision: 1.123 $ * $Date: 2003/03/13 17:51:28 $ * @@ -73,6 +73,10 @@ import java.util.HashSet; import java.util.Set; +import org.apache.commons.httpclient.auth.AuthScheme; +import org.apache.commons.httpclient.auth.AuthenticationException; +import org.apache.commons.httpclient.auth.HttpAuthenticator; +import org.apache.commons.httpclient.auth.MalformedChallengeException; import org.apache.commons.httpclient.cookie.MalformedCookieException; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.cookie.CookieSpec; @@ -895,7 +899,7 @@ if (doAuthentication) { //process authentication response //if the authentication is successful, return the statusCode //otherwise, drop through the switch and try again. - if (processAuthenticationResponse(state)) { + if (processAuthenticationResponse(state, conn)) { return false; } } else { //let the client handle the authenticaiton @@ -987,9 +991,23 @@ try { //pre-emptively add the authorization header, if required. - Authenticator.authenticate(this, state); - if (conn.isProxied()) { - Authenticator.authenticateProxy(this, state); + if (state.isPreemptiveAuthentication()) { + + LOG.debug("Preemptively sending default basic credentials"); + + try { + if (HttpAuthenticator.authenticateDefault(this, state)) { + LOG.debug("Default basic credentials applied"); + } + if (conn.isProxied()) { + if (HttpAuthenticator.authenticateProxyDefault(this, state)) { + LOG.debug("Default basic proxy credentials applied"); + } + } + } catch(AuthenticationException e) { + // Log error and move on + LOG.error(e.getMessage(), e); + } } realms = new HashSet(); @@ -1309,14 +1327,18 @@ + "HttpState, HttpConnection)"); // add authorization header, if needed - if (getRequestHeader(Authenticator.WWW_AUTH_RESP) == null) { - Header wwwAuthenticateHeader = getResponseHeader( - Authenticator.WWW_AUTH); - if (null != wwwAuthenticateHeader) { + if (getRequestHeader(HttpAuthenticator.WWW_AUTH_RESP) == null) { + Header[] challenges = getResponseHeaderGroup().getHeaders( + HttpAuthenticator.WWW_AUTH); + if (challenges.length > 0) { try { - Authenticator.authenticate(this, state); + AuthScheme authscheme = HttpAuthenticator.selectAuthScheme(challenges); + HttpAuthenticator.authenticate(authscheme, this, state); } catch (HttpException e) { - // ignored + // log and move on + if (LOG.isErrorEnabled()) { + LOG.error(e.getMessage(), e); + } } } } @@ -1452,14 +1474,18 @@ + "HttpState, HttpConnection)"); // add proxy authorization header, if needed - if (getRequestHeader(Authenticator.PROXY_AUTH_RESP) == null) { - Header wwwAuthenticateHeader = getResponseHeader( - Authenticator.PROXY_AUTH); - if (null != wwwAuthenticateHeader) { + if (getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP) == null) { + Header[] challenges = getResponseHeaderGroup().getHeaders( + HttpAuthenticator.PROXY_AUTH); + if (challenges.length > 0) { try { - Authenticator.authenticateProxy(this, state); + AuthScheme authscheme = HttpAuthenticator.selectAuthScheme(challenges); + HttpAuthenticator.authenticateProxy(authscheme, this, state); } catch (HttpException e) { - // ignored + // log and move on + if (LOG.isErrorEnabled()) { + LOG.error(e.getMessage(), e); + } } } } @@ -2235,64 +2261,85 @@ * process a response that requires authentication * * @param state the current state + * @param conn The connection * * @return true if the request has completed process, false if more * attempts are needed */ - private boolean processAuthenticationResponse(HttpState state) { + private boolean processAuthenticationResponse(HttpState state, HttpConnection conn) { LOG.trace("enter HttpMethodBase.processAuthenticationResponse(" + "HttpState, HttpConnection)"); int statusCode = statusLine.getStatusCode(); // handle authentication required - Header wwwauth = null; + Header[] challenges = null; Set realmsUsed = null; switch (statusCode) { case HttpStatus.SC_UNAUTHORIZED: - wwwauth = getResponseHeader(Authenticator.WWW_AUTH); + challenges = getResponseHeaderGroup().getHeaders(HttpAuthenticator.WWW_AUTH); realmsUsed = realms; break; case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - wwwauth = getResponseHeader(Authenticator.PROXY_AUTH); + challenges = getResponseHeaderGroup().getHeaders(HttpAuthenticator.PROXY_AUTH); realmsUsed = proxyRealms; break; } boolean authenticated = false; // if there was a header requesting authentication - if (null != wwwauth) { - String pathAndCreds = getPath() + ":" + wwwauth.getValue(); - if (realmsUsed.contains(pathAndCreds)) { + if (challenges.length > 0) { + AuthScheme authscheme = null; + try { + authscheme = HttpAuthenticator.selectAuthScheme(challenges); + } catch(MalformedChallengeException e) { + if (LOG.isErrorEnabled()) { + LOG.error(e.getMessage(), e); + } + return true; + } catch (UnsupportedOperationException e) { + if (LOG.isErrorEnabled()) { + LOG.error(e.getMessage(), e); + } + return true; + } + + StringBuffer buffer = new StringBuffer(); + buffer.append(conn.getHost()); + int port = conn.getPort(); + if (conn.getProtocol().getDefaultPort() != port) { + buffer.append(':'); + buffer.append(port); + } + buffer.append('#'); + buffer.append(authscheme.getRealm()); + String realm = buffer.toString(); + + if (realmsUsed.contains(realm)) { if (LOG.isInfoEnabled()) { LOG.info("Already tried to authenticate to \"" - + wwwauth.getValue() + "\" but still receiving " + + realm + "\" but still receiving " + statusCode + "."); } return true; } else { - realmsUsed.add(pathAndCreds); + realmsUsed.add(realm); } try { //remove preemptive header and reauthenticate switch (statusCode) { case HttpStatus.SC_UNAUTHORIZED: - removeRequestHeader(Authenticator.WWW_AUTH_RESP); - authenticated = Authenticator.authenticate(this, state); + removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); + authenticated = HttpAuthenticator.authenticate(authscheme, this, state); break; case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - removeRequestHeader(Authenticator.PROXY_AUTH_RESP); - authenticated = Authenticator.authenticateProxy(this, - state); + removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); + authenticated = HttpAuthenticator.authenticateProxy(authscheme, this, state); break; } - } catch (HttpException httpe) { - LOG.warn(httpe.getMessage()); + } catch (AuthenticationException e) { + LOG.warn(e.getMessage()); return true; // finished request - } catch (UnsupportedOperationException uoe) { - LOG.warn(uoe.getMessage()); - //FIXME: should this return true? } - if (!authenticated) { // won't be able to authenticate to this challenge // without additional information Index: java/org/apache/commons/httpclient/HttpState.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpState.java,v retrieving revision 1.17 diff -u -r1.17 HttpState.java --- java/org/apache/commons/httpclient/HttpState.java 28 Jan 2003 22:25:21 -0000 1.17 +++ java/org/apache/commons/httpclient/HttpState.java 21 Mar 2003 10:56:58 -0000 @@ -64,6 +64,7 @@ package org.apache.commons.httpclient; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -81,6 +82,12 @@ * to request, such as {@link Cookie}s and authentication * {@link Credentials}. *

+ *

+ * Preemptive authentication can be turned on by using the property value of + * #PREEMPTIVE_PROPERTY. If left unspecified, it has the default value of + * #PREEMPTIVE_DEFAULT. This configurable behaviour conforms to rcf2617: + *

+ * * @author Remy Maucherat * @author Rodney Waldhoff * @author Jeff Dever @@ -97,6 +104,22 @@ // ----------------------------------------------------- Instance Variables /** + * Whether I should attempt to authenticate preemptively. + */ + private boolean preemptive; + + /** + * The boolean property name to turn on preemptive authentication. + */ + public static final String PREEMPTIVE_PROPERTY = + "httpclient.authentication.preemptive"; + + /** + * The default property value for #PREEMPTIVE_PROPERTY. + */ + public static final String PREEMPTIVE_DEFAULT = "false"; + + /** * My {@link Credentials Credentials}s, by realm. */ private HashMap credMap = new HashMap(); @@ -132,7 +155,22 @@ this.httpConnectionManager = new SimpleHttpConnectionManager(); this.cookiePolicy = CookiePolicy.getDefaultPolicy(); - + + // check the preemptive policy + // TODO: this needs to be a service from some configuration class + String preemptiveDefault = + System.getProperties().getProperty(PREEMPTIVE_PROPERTY, + PREEMPTIVE_DEFAULT); + preemptiveDefault = preemptiveDefault.trim().toLowerCase(); + + if (!(preemptiveDefault.equals("true") + || preemptiveDefault.equals("false"))) { // property problem + LOG.warn("Configuration property " + PREEMPTIVE_PROPERTY + + " must be either true or false. Using default: " + + PREEMPTIVE_DEFAULT); + preemptiveDefault = PREEMPTIVE_DEFAULT; + } + this.preemptive = ("true".equals(preemptiveDefault)); } // ------------------------------------------------------------- Properties @@ -306,6 +344,30 @@ /** + * Defines whether preemptive authentication should be + * attempted or not. + * + * @param value boolean flag + */ + + public void setUsePreemptiveAuthentication(boolean value) { + this.preemptive = value; + } + + + /** + * Return true if preemptive authentication should be + * attempted, otherwise return false + * + * @return boolean flag. + */ + + public boolean isPreemptiveAuthentication() { + return this.preemptive; + } + + + /** * Set the {@link CookiePolicy} to one of {@link * CookiePolicy#COMPATIBILITY}, {@link CookiePolicy#NETSCAPE_DRAFT} or * {@link CookiePolicy#RFC2109} @@ -364,6 +426,21 @@ return creds; } + /** + * Get a collection of all {@link Credentials} + * keyed by authentication realm. + * + * @return the map of all credentials + * + * @see #getCredentials(String) + * @see #setCredentials(String, Credentials) + * + */ + public synchronized Map getAllCredentials() { + LOG.trace("enter HttpState.getAllCredentials()"); + + return (Map)this.credMap.clone(); + } /** * Set the for the proxy with the given authentication realm. @@ -411,6 +488,22 @@ } /** + * Get a collection of all proxy {@link Credentials} + * keyed by authentication realm. + * + * @return the map of all proxy credentials + * + * @see #getProxyCredentials(String) + * @see #setProxyCredentials(String, Credentials) + * + */ + public synchronized Map getAllProxyCredentials() { + LOG.trace("enter HttpState.getAllProxyCredentials()"); + + return (Map)this.proxyCred.clone(); + } + + /** * Return a string representation of this object. * @return The string representation. * @see java.lang.Object#toString() @@ -512,5 +605,4 @@ ) { this.httpConnectionManager = httpConnectionManager; } - } Index: java/org/apache/commons/httpclient/MalformedChallengeException.java =================================================================== RCS file: java/org/apache/commons/httpclient/MalformedChallengeException.java diff -N java/org/apache/commons/httpclient/MalformedChallengeException.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/MalformedChallengeException.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,87 @@ +/* + * ==================================================================== + * + * 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 org.apache.commons.httpclient.HttpException; + +/** + * Signals that authentication challenge is in some way invalid or + * illegal in the given context + * + * @author Oleg Kalnichevski + * + * @since 2.0 + */ +public class MalformedChallengeException extends HttpException { + + /** + * @see HttpException#HttpException() + */ + public MalformedChallengeException() { + super(); + } + + /** + * @see HttpException#HttpException(String) + */ + public MalformedChallengeException(String message) { + super(message); + } +} Index: java/org/apache/commons/httpclient/NTLMScheme.java =================================================================== RCS file: java/org/apache/commons/httpclient/NTLMScheme.java diff -N java/org/apache/commons/httpclient/NTLMScheme.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/NTLMScheme.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,206 @@ +/* + * ==================================================================== + * + * 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 org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.NTLM; +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.NTCredentials; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * Microsoft proprietary NTLM authentication scheme. + *

+ * + * @author Remy Maucherat + * @author Rodney Waldhoff + * @author Jeff Dever + * @author Ortwin Gl� + * @author Sean C. Sullivan + * @author Adrian Sutton + * @author Mike Bowler + * @author Oleg Kalnichevski + */ +public class NTLMScheme extends AuthSchemeBase { + + /** Log object for this class. */ + private static final Log LOG = LogFactory.getLog(NTLMScheme.class); + + /** NTLM challenge string. */ + private String ntmlchallenge = null; + + /** + * Constructor for the NTLM authentication scheme. + * + * @param challenge The authentication challenge + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public NTLMScheme(final String challenge) throws MalformedChallengeException { + super(challenge); + String s = AuthChallengeParser.extractScheme(challenge); + if (!s.equalsIgnoreCase(getSchemeName())) { + throw new MalformedChallengeException("Invalid NTLM challenge: " + challenge); + } + int i = challenge.indexOf(' '); + if (i != -1) { + s = challenge.substring(i, challenge.length()); + this.ntmlchallenge = s.trim(); + } else { + this.ntmlchallenge = ""; + } + } + + /** + * Returns textual designation of the NTLM authentication scheme. + * + * @return NTLM + */ + public String getSchemeName() { + return "NTLM"; + } + + /** + * The concept of an authentication realm is not supported by the NTLM + * authentication scheme. Always returns null. + * + * @return null + */ + public String getRealm() { + return null; + } + + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return the parameter with the given name + */ + public String getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + return null; + } + + /** + * Create a NTLM authorization string for the given + * challenge and NT credentials. + * + * @param challenge The challenge. + * @param credentials {@link NTCredentials} + * + * @return a ntlm authorization string + * @throws AuthenticationException is thrown if authentication fails + */ + public static String authenticate( + final NTCredentials credentials, final String challenge) + throws AuthenticationException { + + LOG.trace("enter NTLMScheme.authenticate(NTCredentials, String)"); + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + + NTLM ntlm = new NTLM(); + String s = null; + try { + s = ntlm.getResponseFor(challenge, + credentials.getUserName(), credentials.getPassword(), + credentials.getHost(), credentials.getDomain()); + } catch(HttpException e) { + throw new AuthenticationException(e.getMessage()); + } + return "NTLM " + s; + } + + /** + * Produces NTLM authorization string for the given set of + * {@link Credentials}. + * + * @param credentials The set of credentials to be used for athentication + * @param method Method name is ignored by the NTLM authentication scheme + * @param uri URI is ignored by the NTLM authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return an NTLM authorization string + */ + public String authenticate(Credentials credentials, String method, String uri) + throws AuthenticationException { + LOG.trace("enter NTLMScheme.authenticate(Credentials, String, String)"); + + NTCredentials ntcredentials = null; + try { + ntcredentials = (NTCredentials)credentials; + } catch(ClassCastException e ) { + throw new AuthenticationException( + "Credentials cannot be used for basic authentication: " + + credentials.toString()); + } + return NTLMScheme.authenticate(ntcredentials, this.ntmlchallenge); + } +} Index: java/org/apache/commons/httpclient/RFC2617Scheme.java =================================================================== RCS file: java/org/apache/commons/httpclient/RFC2617Scheme.java diff -N java/org/apache/commons/httpclient/RFC2617Scheme.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/RFC2617Scheme.java 21 Mar 2003 10:56:59 -0000 @@ -0,0 +1,127 @@ +/* + * ==================================================================== + * + * 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.Map; + +/** + *

+ * Abstract authentication scheme class that lays foundation for all + * RFC 2617 compliant authetication schemes and provides capabilities common + * to all authentication schemes defined in RFC 2617. + *

+*/ +public abstract class RFC2617Scheme extends AuthSchemeBase { + + /** + * Authentication parameter map. + */ + private Map params = null; + + /** + * Default constructor for RFC2617 compliant authetication schemes. + * + * @param challenge authentication challenge + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public RFC2617Scheme(final String challenge) throws MalformedChallengeException { + super(challenge); + String s = AuthChallengeParser.extractScheme(challenge); + if (!s.equalsIgnoreCase(getSchemeName())) { + throw new MalformedChallengeException( + "Invalid " + getSchemeName() +" challenge: " + challenge); + } + this.params = AuthChallengeParser.extractParams(challenge); + } + + /** + * Returns authentication parameters map. Keys in the map are lower-cased. + * + * @return the map of authentication parameters + */ + protected Map getParameters() { + return this.params; + } + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return the parameter with the given name + */ + public String getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + return (String)this.params.get(name.toLowerCase()); + } + + /** + * Returns authentication realm. The realm may not be null. + * + * @return the authentication realm + */ + public String getRealm() { + return getParameter("realm"); + } +} Index: test/org/apache/commons/httpclient/TestAuthenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v retrieving revision 1.21 diff -u -r1.21 TestAuthenticator.java --- test/org/apache/commons/httpclient/TestAuthenticator.java 23 Jan 2003 22:48:25 -0000 1.21 +++ test/org/apache/commons/httpclient/TestAuthenticator.java 21 Mar 2003 10:56:58 -0000 @@ -68,6 +68,7 @@ import java.util.Hashtable; import java.util.StringTokenizer; +import org.apache.commons.httpclient.auth.*; import org.apache.commons.httpclient.util.Base64; /** @@ -108,7 +109,7 @@ } String response = (String) table.get("response"); table.put( "methodname", methodName ); - String digest = Authenticator.createDigest(cred.getUserName(),cred.getPassword(), table); + String digest = DigestScheme.createDigest(cred.getUserName(),cred.getPassword(), table); assertEquals(response, digest); } @@ -120,7 +121,7 @@ } - // ---------------------------------- Test Methods for Basic Authentication + // ---------------------------------- Test Methods for BasicScheme Authentication public void testBasicAuthenticationWithNoCreds() { HttpState state = new HttpState(); @@ -166,8 +167,8 @@ HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate","Basic realm=\"realm1\"")); try { Authenticator.authenticate(method,(HttpState)null); - fail("Should have thrown NullPointerException"); - } catch(NullPointerException e) { + fail("Should have thrown IllegalArgumentException"); + } catch(IllegalArgumentException e) { // expected } } @@ -249,7 +250,7 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(); - System.getProperties().setProperty(Authenticator.PREEMPTIVE_PROPERTY, "true"); + state.setUsePreemptiveAuthentication(true); assertTrue(! Authenticator.authenticate(method,state)); assertTrue(null == method.getRequestHeader("Authorization")); } @@ -259,7 +260,7 @@ HttpMethod method = new SimpleHttpMethod(); state.setCredentials(null, new UsernamePasswordCredentials("username","password")); - System.getProperties().setProperty(Authenticator.PREEMPTIVE_PROPERTY, "true"); + state.setUsePreemptiveAuthentication(true); assertTrue(Authenticator.authenticate(method,state)); assertTrue(null != method.getRequestHeader("Authorization")); String expected = "Basic " + HttpConstants.getString(Base64.encode(HttpConstants.getBytes("username:password"))); @@ -270,13 +271,13 @@ HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(); - System.getProperties().setProperty(Authenticator.PREEMPTIVE_PROPERTY, "false"); + state.setUsePreemptiveAuthentication(false); state.setCredentials(null, new UsernamePasswordCredentials("username","password")); assertTrue(! Authenticator.authenticate(method,state)); assertTrue(null == method.getRequestHeader("Authorization")); } - // --------------------------------- Test Methods for Digest Authentication + // --------------------------------- Test Methods for DigestScheme Authentication public void testDigestAuthenticationWithNoCreds() { HttpState state = new HttpState(); @@ -315,8 +316,8 @@ HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate","Digest realm=\"realm1\"")); try { Authenticator.authenticate(method,(HttpState)null); - fail("Should have thrown NullPointerException"); - } catch(NullPointerException e) { + fail("Should have thrown IllegalArgumentException"); + } catch(IllegalArgumentException e) { // expected } } @@ -392,8 +393,8 @@ method.addRequestHeader("Host", "host"); try { Authenticator.authenticate(method,(HttpState)null); - fail("Should have thrown NullPointerException"); - } catch(NullPointerException e) { + fail("Should have thrown IllegalArgumentException"); + } catch(IllegalArgumentException e) { // expected } } Index: test/org/apache/commons/httpclient/TestChallengeParser.java =================================================================== RCS file: test/org/apache/commons/httpclient/TestChallengeParser.java diff -N test/org/apache/commons/httpclient/TestChallengeParser.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/org/apache/commons/httpclient/TestChallengeParser.java 21 Mar 2003 10:56:57 -0000 @@ -0,0 +1,116 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-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", "Tomcat", 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; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.util.Hashtable; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.httpclient.auth.AuthChallengeParser; +import org.apache.commons.httpclient.auth.MalformedChallengeException; + +/** + * Unit tests for {@link AuthChallengeParser}. + * + * @author Oleg Kalnichevski + */ +public class TestChallengeParser extends TestCase { + + // ------------------------------------------------------------ Constructor + public TestChallengeParser(String testName) { + super(testName); + } + + // ------------------------------------------------------------------- Main + public static void main(String args[]) { + String[] testCaseName = { TestChallengeParser.class.getName() }; + junit.textui.TestRunner.main(testCaseName); + } + + // ------------------------------------------------------- TestCase Methods + + public static Test suite() { + return new TestSuite(TestChallengeParser.class); + } + + + public void testParsingChallenge() { + String challenge = + "Basic realm=\"realm1\", test, test1 = stuff, test2 = \"stuff, stuff\", test3=\"crap"; + String scheme = null; + Map elements = null; + try { + scheme = AuthChallengeParser.extractScheme(challenge); + elements = AuthChallengeParser.extractParams(challenge); + } catch (MalformedChallengeException e) { + fail("Unexpected exception: " + e.toString()); + } + assertEquals("basic", scheme); + assertEquals("realm1", elements.get("realm")); + assertEquals(null, elements.get("test")); + assertEquals("stuff", elements.get("test1")); + assertEquals("stuff, stuff", elements.get("test2")); + assertEquals("\"crap", elements.get("test3")); + } +} Index: test/org/apache/commons/httpclient/TestNoHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v retrieving revision 1.20 diff -u -r1.20 TestNoHost.java --- test/org/apache/commons/httpclient/TestNoHost.java 13 Mar 2003 17:51:28 -0000 1.20 +++ test/org/apache/commons/httpclient/TestNoHost.java 21 Mar 2003 10:56:58 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v 1.20 2003/03/13 17:51:28 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v 1.20 2003/03/13 17:51:28 olegk Exp $ * $Revision: 1.20 $ * $Date: 2003/03/13 17:51:28 $ * ==================================================================== @@ -88,6 +88,7 @@ suite.addTest(TestNVP.suite()); suite.addTest(TestHeader.suite()); suite.addTest(TestHeaderElement.suite()); + suite.addTest(TestChallengeParser.suite()); suite.addTest(TestAuthenticator.suite()); suite.addTest(TestHttpUrlMethod.suite()); suite.addTest(TestURI.suite()); Index: test/org/apache/commons/httpclient/TestWebappBasicAuth.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappBasicAuth.java,v retrieving revision 1.11 diff -u -r1.11 TestWebappBasicAuth.java --- test/org/apache/commons/httpclient/TestWebappBasicAuth.java 13 Mar 2003 01:23:37 -0000 1.11 +++ test/org/apache/commons/httpclient/TestWebappBasicAuth.java 21 Mar 2003 10:56:58 -0000 @@ -254,7 +254,6 @@ public void testHeadAuth() throws Exception { HttpClient client = new HttpClient(); HttpState state = client.getState(); - System.setProperty(Authenticator.PREEMPTIVE_PROPERTY, Authenticator.PREEMPTIVE_DEFAULT); Credentials cred = new UsernamePasswordCredentials("jakarta", "commons"); state.setCredentials(null, cred); HostConfiguration hc = new HostConfiguration();