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.41 diff -u -r1.41 Authenticator.java --- java/org/apache/commons/httpclient/Authenticator.java 28 Mar 2003 16:37:49 -0000 1.41 +++ java/org/apache/commons/httpclient/Authenticator.java 3 Apr 2003 12:57:53 -0000 @@ -199,12 +199,17 @@ * @see HttpMethod#addRequestHeader */ private static boolean authenticate(HttpMethod method, HttpState state, - boolean proxy) + boolean proxy) throws HttpException, UnsupportedOperationException { LOG.trace("enter Authenticator.authenticate(HttpMethod, HttpState, " + "Header, String)"); + return authenticate(method, null, state, proxy); + } + private static boolean authenticate(HttpMethod method, HttpConnection conn, + HttpState state, boolean proxy) + throws HttpException, UnsupportedOperationException { String challengeheader = proxy ? PROXY_AUTH : WWW_AUTH; // I REALLY hate doing this, but I need to avoid multiple autorization @@ -240,9 +245,9 @@ LOG.debug("Using " + authscheme.getSchemeName() + " authentication scheme"); } if (proxy) { - return HttpAuthenticator.authenticateProxy(authscheme, method, state); + return HttpAuthenticator.authenticateProxy(authscheme, method, conn, state); } else { - return HttpAuthenticator.authenticate(authscheme, method, state); + return HttpAuthenticator.authenticate(authscheme, method, conn, state); } } 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.127 diff -u -r1.127 HttpMethodBase.java --- java/org/apache/commons/httpclient/HttpMethodBase.java 1 Apr 2003 00:25:24 -0000 1.127 +++ java/org/apache/commons/httpclient/HttpMethodBase.java 3 Apr 2003 12:58:07 -0000 @@ -76,7 +76,6 @@ import org.apache.commons.httpclient.cookie.MalformedCookieException; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.cookie.CookieSpec; -import org.apache.commons.httpclient.cookie.MalformedCookieException; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.util.URIUtil; import org.apache.commons.logging.Log; @@ -1338,7 +1337,7 @@ if (challenges.length > 0) { try { AuthScheme authscheme = HttpAuthenticator.selectAuthScheme(challenges); - HttpAuthenticator.authenticate(authscheme, this, state); + HttpAuthenticator.authenticate(authscheme, this, conn, state); } catch (HttpException e) { // log and move on if (LOG.isErrorEnabled()) { @@ -1485,7 +1484,7 @@ if (challenges.length > 0) { try { AuthScheme authscheme = HttpAuthenticator.selectAuthScheme(challenges); - HttpAuthenticator.authenticateProxy(authscheme, this, state); + HttpAuthenticator.authenticateProxy(authscheme, this, conn, state); } catch (HttpException e) { // log and move on if (LOG.isErrorEnabled()) { @@ -2340,11 +2339,11 @@ switch (statusCode) { case HttpStatus.SC_UNAUTHORIZED: removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); - authenticated = HttpAuthenticator.authenticate(authscheme, this, state); + authenticated = HttpAuthenticator.authenticate(authscheme, this, conn, state); break; case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); - authenticated = HttpAuthenticator.authenticateProxy(authscheme, this, state); + authenticated = HttpAuthenticator.authenticateProxy(authscheme, this, conn, state); break; } } catch (AuthenticationException e) { 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.18 diff -u -r1.18 HttpState.java --- java/org/apache/commons/httpclient/HttpState.java 27 Mar 2003 20:58:27 -0000 1.18 +++ java/org/apache/commons/httpclient/HttpState.java 3 Apr 2003 12:58:11 -0000 @@ -64,15 +64,15 @@ 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; -import java.util.List; import java.util.Iterator; +import java.util.List; +import java.util.Map; -import org.apache.commons.httpclient.cookie.CookieSpec; +import org.apache.commons.httpclient.auth.HttpAuthRealm; import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.cookie.CookieSpec; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -378,7 +378,6 @@ this.cookiePolicy = policy; } - /** * Set the Credentials for the given authentication realm. * @@ -390,40 +389,121 @@ *

* Any previous credentials for this realm will be overwritten. * + * @deprecated This method does not distinguish between realms with the + * same name on different hosts. Use + * {@link HttpState#setCredentials(String, Credentials)} instead. + * * @param realm the authentication realm * @param credentials the authentication credentials for the given realm * - * @see #getCredentials(String) - * @see #setProxyCredentials(String, Credentials) + * @see #getCredentials(String, String) + * @see #setProxyCredentials(String, String, Credentials) * */ + public synchronized void setCredentials(String realm, Credentials credentials) { LOG.trace("enter HttpState.setCredentials(String, Credentials)"); - credMap.put(realm, credentials); + setCredentials(realm, null, credentials); + } + + /** Sets the credentials for realm on host. + * with no host. + * + * When realm is null, I'll use the given + * credentials when no other {@link Credentials} have + * been supplied for the given challenging realm. + * (I.e., use a null realm to set the "default" + * credentials.) + *

+ * Any previous credentials for this realm will be overwritten. + * + * @param realm the authentication realm + * @param host the host the realm belongs to + * @param credentials the authentication credentials for the given realm. + * + * @see #getCredentials(String, String) + * @see #setProxyCredentials(String, String, Credentials) + */ + + public synchronized void setCredentials(String realm, String host, Credentials credentials) { + LOG.trace( + "enter HttpState.setCredentials(String realm, String host, Credentials credentials)"); + credMap.put(new HttpAuthRealm(host, realm), credentials); + /* + if (host != null) { + if (realm == null) { + credMap.put(host.toLowerCase(), credentials); + } else { + credMap.put(host.toLowerCase() + realm, credentials); + } + } else { + credMap.put(realm, credentials); + }*/ + } + + /** + * Get the Credentials for the given authentication realm. + * + * If the realm exists on host, return the coresponding credentials. + * If the host exists with a null realm, return the corresponding + * credentials. + * If the realm exists with a null host, return the + * corresponding credentials. If the realm does not exist, return + * the default Credentials. If there are no default credentials, return + * null. + * + * @param realm the authentication realm + * @param host the host the realm is on + * @return the credentials + * + * @see #setCredentials(String, String, Credentials) + * + */ + + public synchronized Credentials getCredentials(String realm, String host) { + LOG.trace("enter HttpState.getCredentials(String, String"); + HttpAuthRealm entry = new HttpAuthRealm(host, realm); + Credentials creds = (Credentials) credMap.get(entry); + if (creds == null && host != null && realm != null) { + entry = new HttpAuthRealm(host, null); + creds = (Credentials) credMap.get(entry); + if (creds == null) { + entry = new HttpAuthRealm(null, realm); + creds = (Credentials) credMap.get(entry); + } + } + + if (creds == null) { + entry = new HttpAuthRealm(null, null); + creds = (Credentials) credMap.get(entry); + } + return creds; } - /** * Get the Credentials for the given authentication realm. * - * If the realm exists, return the coresponding credentials. If the - * realm does not exist, return the default Credentials. If there is - * no default credentials, return null. + * If the realm exists on host, return the coresponding credentials. + * If the realm exists with a null host, return the + * corresponding credentials. If the realm does not exist, return + * the default Credentials. If there is no default credentials, return + * null. * + * @deprecated This method does not distinguish between realms on different + * servers with the same name. Use {@link #getCredentials(String, String)} + * instead. + * * @param realm the authentication realm * @return the credentials * - * @see #setCredentials(String, Credentials) + * @see #setCredentials(String, String, Credentials) * */ + public synchronized Credentials getCredentials(String realm) { LOG.trace("enter HttpState.getCredentials(String)"); - Credentials creds = (Credentials) credMap.get(realm); - if (creds == null) { - creds = (Credentials) credMap.get(null); - } - return creds; + return getCredentials(realm, null); } /** @@ -438,6 +518,10 @@ *

* Any previous credentials for this realm will be overwritten. * + * @depreciated This method does not differentiate between realms with + * the same name on different servers. Use + * {@link #setProxyCredentials(String, String, Credentials)} instead. + * * @param realm the authentication realm * @param credentials the authentication credentials for the given realm * @@ -445,11 +529,36 @@ * @see #setCredentials(String, Credentials) * */ + public synchronized void setProxyCredentials(String realm, Credentials credentials) { LOG.trace("enter HttpState.setProxyCredentials(String, credentials)"); - proxyCred.put(realm, credentials); + setProxyCredentials(realm, null, credentials); + } + + /** + * Set the for the proxy with the given authentication realm. + * + * When realm and host are null, I'll use the given + * credentials when no other {@link Credentials} have + * been supplied for the given challenging realm. + * (I.e., use a null realm to set the "default" + * credentials.) Realms rarely make much sense with proxies, so + * null is normally a good choice here. + *

+ * Any previous credentials for this realm will be overwritten. + * + * @param realm the authentication realm + * @param credentials the authentication credentials for the given realm + * + * @see #getProxyCredentials(String) + * @see #setCredentials(String, Credentials) + * + */ + + public synchronized void setProxyCredentials(String realm, String proxyHost, Credentials credentials) { + LOG.trace("enter HttpState.setProxyCredentials(String, String, Credentials"); + proxyCred.put(new HttpAuthRealm(proxyHost, realm), credentials); } - /** * Get the Credentials for the proxy with the given authentication realm. @@ -457,19 +566,60 @@ * If the realm exists, return the coresponding credentials. If the * realm does not exist, return the default Credentials. If there is * no default credentials, return null. + * + * @deprecated This method does not distinguish between realms on different hosts. + * Use {@link #getProxyCredentials(String, String)} instead. * * @param realm the authentication realm * @return the credentials - * @see #setProxyCredentials + * @see #setProxyCredentials(String, String, Credentials) */ + public synchronized Credentials getProxyCredentials(String realm) { LOG.trace("enter HttpState.getProxyCredentials(String)"); - Credentials creds = (Credentials) proxyCred.get(realm); + return getProxyCredentials(realm, null); + } + + /** + * Get the Credentials for the proxy with the given authentication realm on the given + * host. + * + * If the realm exists on host, return the coresponding credentials. + * If the host exists with a null realm, return the corresponding + * credentials. + * If the realm exists with a null host, return the + * corresponding credentials. If the realm does not exist, return + * the default Credentials. If there are no default credentials, return + * null. + * + * @deprecated This method does not distinguish between realms on different hosts. + * Use {@link #getProxyCredentials(String, String)} instead. + * + * @param realm the authentication realm + * @param host the host the realm is on + * @return the credentials + * @see #setProxyCredentials(String, String, Credentials) + */ + + public synchronized Credentials getProxyCredentials(String realm, String proxyHost) { + LOG.trace("enter HttpState.getCredentials(String, String"); + HttpAuthRealm entry = new HttpAuthRealm(proxyHost, realm); + Credentials creds = (Credentials) proxyCred.get(entry); + if (creds == null && proxyHost != null && realm != null) { + entry = new HttpAuthRealm(proxyHost, null); + creds = (Credentials) proxyCred.get(entry); + if (creds == null) { + entry = new HttpAuthRealm(null, realm); + creds = (Credentials) proxyCred.get(entry); + } + } + if (creds == null) { - creds = (Credentials) proxyCred.get(null); + entry = new HttpAuthRealm(null, null); + creds = (Credentials) proxyCred.get(entry); } return creds; - } + } /** * Return a string representation of this object. @@ -501,7 +651,7 @@ StringBuffer sbResult = new StringBuffer(); Iterator iter = proxyCredMap.keySet().iterator(); while (iter.hasNext()) { - String key = (String) iter.next(); + Object key = iter.next(); Credentials cred = (Credentials) proxyCredMap.get(key); if (sbResult.length() > 0) { sbResult.append(", "); @@ -522,7 +672,7 @@ StringBuffer sbResult = new StringBuffer(); Iterator iter = credMap.keySet().iterator(); while (iter.hasNext()) { - String key = (String) iter.next(); + Object key = iter.next(); Credentials cred = (Credentials) credMap.get(key); if (sbResult.length() > 0) { sbResult.append(", "); Index: java/org/apache/commons/httpclient/auth/HttpAuthRealm.java =================================================================== RCS file: java/org/apache/commons/httpclient/auth/HttpAuthRealm.java diff -N java/org/apache/commons/httpclient/auth/HttpAuthRealm.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/auth/HttpAuthRealm.java 3 Apr 2003 12:58:15 -0000 @@ -0,0 +1,91 @@ +package org.apache.commons.httpclient.auth; + +/** The key used to look up authentication credentials. + * + * @author Oleg Kalnichevski + * @author Adrian Sutton + */ +public class HttpAuthRealm { + + /** The realm the credentials apply to. */ + private String realm = null; + + /** The domain the credentials apply to. */ + private String domain = null; + + /** Creates a new HttpAuthRealm for the given domain and + * realm. + * + * @param domain the domain the credentials apply to + * @param realm the realm the credentials apply to + */ + public HttpAuthRealm(final String domain, final String realm) { + this.domain = domain; + this.realm = realm; + } + + /** Determines if the given domains match. Note that null acts as a + * wildcard so if either of the domains are null, it is considered a match. + * + * @param d1 the domain + * @param d2 the other domain + * @return boolean true if the domains match, otherwise false. + */ + private static boolean domainAttribMatch(final String d1, final String d2) { + return d1 == null || d2 == null || d1.equalsIgnoreCase(d2); + } + + /** Determines if the given realms match. Note that null acts as a + * wildcard so if either realm is null, this function will return true. + * + * @param r1 the realm + * @param r2 the other realm + * @return boolean true if the realms match, otherwise false. + */ + private static boolean realmAttribMatch(final String r1, final String r2) { + return r1 == null || r2 == null || r1.equals(r2); + } + + + /** + * @see java.lang.Object#equals(Object) + */ + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o == this) { + return true; + } + if (!(o instanceof HttpAuthRealm)) { + return super.equals(o); + } + HttpAuthRealm that = (HttpAuthRealm)o; + return + domainAttribMatch(this.domain, that.domain) && + realmAttribMatch(this.realm, that.realm); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("Authentication domain: '"); + buffer.append(this.domain); + buffer.append("', authentication realm: '"); + buffer.append(this.realm); + buffer.append("'"); + return buffer.toString(); + } + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + StringBuffer buffer = new StringBuffer(); + buffer.append(this.domain); + buffer.append(this.realm); + return buffer.toString().hashCode(); + } + +} \ No newline at end of file Index: java/org/apache/commons/httpclient/auth/HttpAuthenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java,v retrieving revision 1.1 diff -u -r1.1 HttpAuthenticator.java --- java/org/apache/commons/httpclient/auth/HttpAuthenticator.java 27 Mar 2003 20:58:27 -0000 1.1 +++ java/org/apache/commons/httpclient/auth/HttpAuthenticator.java 3 Apr 2003 12:58:18 -0000 @@ -62,6 +62,7 @@ import java.util.Map; import java.util.HashMap; import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpConnection; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.HttpState; @@ -246,10 +247,30 @@ return doAuthenticateDefault(method, state, true); } + /** + * Attempt to provide authentication credentials + * to the given method in the given state using + * the given authentication scheme. + * + * @param authscheme the authentication scheme to use + * @param method the HttpMethod which requires authentication + * @param conn the connection being used + * @param state the HttpState object providing Credentials + * + * @return true if an authentication response header + * was added + * + * @throws AuthenticationException when a parsing or other error occurs - private static boolean doAuthenticate(AuthScheme authscheme, HttpMethod method, HttpState state, - boolean proxy) + * @see HttpState#setCredentials(String, String, Credentials) + */ + private static boolean doAuthenticate(AuthScheme authscheme, HttpMethod method, HttpConnection conn, + HttpState state, boolean proxy) throws AuthenticationException { + String host = null; + if (conn != null) { + host = proxy ? conn.getProxyHost() : conn.getHost(); + } if (authscheme == null) { throw new IllegalArgumentException("Authentication scheme may not be null"); } @@ -273,8 +294,9 @@ LOG.debug("Using '" + realm + "' authentication realm"); } } + Credentials credentials = proxy ? - state.getProxyCredentials(realm) : state.getCredentials(realm); + state.getProxyCredentials(realm, host) : state.getCredentials(realm, host); if (credentials == null) { throw new AuthenticationException( "No credentials available for the " + authscheme.getSchemeName() + @@ -308,8 +330,29 @@ */ public static boolean authenticate(AuthScheme authscheme, HttpMethod method, HttpState state) throws AuthenticationException { + return authenticate(authscheme, method, null, state); + } + + /** + * Attempt to provide requisite authentication credentials to the + * given method in the given state using the given + * authentication scheme and host. + * + * @param authscheme The authentication scheme to be used + * @param method The HttpMethod which requires authentication + * @param conn The connection being used + * @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, HttpConnection conn, + HttpState state) throws AuthenticationException { LOG.trace("enter HttpAuthenticator.authenticate(AuthScheme, HttpMethod, HttpState)"); - return doAuthenticate(authscheme, method, state, false); + return doAuthenticate(authscheme, method, conn, state, false); } @@ -320,6 +363,7 @@ * * @param authscheme The authentication scheme to be used * @param method the HttpMethod which requires authentication + * @param conn the HttpConnection being used * @param state the HttpState object providing Credentials * * @return true if the Proxy-Authenticate response header @@ -329,9 +373,9 @@ * @see HttpState#setCredentials(String,Credentials) */ - public static boolean authenticateProxy(AuthScheme authscheme, HttpMethod method, HttpState state) + public static boolean authenticateProxy(AuthScheme authscheme, HttpMethod method, HttpConnection conn, HttpState state) throws AuthenticationException { LOG.trace("enter HttpAuthenticator.authenticateProxy(AuthScheme, HttpMethod, HttpState)"); - return doAuthenticate(authscheme, method, state, true); + return doAuthenticate(authscheme, method, conn, state, true); } } 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.23 diff -u -r1.23 TestAuthenticator.java --- test/org/apache/commons/httpclient/TestAuthenticator.java 28 Mar 2003 16:37:50 -0000 1.23 +++ test/org/apache/commons/httpclient/TestAuthenticator.java 3 Apr 2003 12:58:24 -0000 @@ -666,5 +666,69 @@ } - + // --------------------------------- Test Methods for Selecting Credentials + + public void testDefaultCredentials() throws Exception { + HttpState state = new HttpState(); + Credentials expected = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials(null, expected); + Credentials got = state.getCredentials("realm", "host"); + assertEquals(got, expected); + } + + public void testRealmCredentials() throws Exception { + HttpState state = new HttpState(); + Credentials expected = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials("realm", expected); + Credentials got = state.getCredentials("realm", "host"); + assertEquals(expected, got); + } + + public void testHostCredentials() throws Exception { + HttpState state = new HttpState(); + Credentials expected = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials(null, "host", expected); + Credentials got = state.getCredentials("realm", "host"); + assertEquals(expected, got); + } + + public void testBothCredentials() throws Exception { + HttpState state = new HttpState(); + Credentials expected = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials("realm", "host", expected); + Credentials got = state.getCredentials("realm", "host"); + assertEquals(expected, got); + } + + public void testWrongHostCredentials() throws Exception { + HttpState state = new HttpState(); + Credentials expected = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials(null, "host1", expected); + Credentials got = state.getCredentials("realm", "host2"); + assertNotSame(expected, got); + } + + public void testWrongRealmCredentials() throws Exception { + HttpState state = new HttpState(); + Credentials cred = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials("realm1", "host", cred); + Credentials got = state.getCredentials("realm2", "host"); + assertNotSame(cred, got); + } + + public void testRealmSpoof() throws Exception { + HttpState state = new HttpState(); + Credentials cred = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials(null, "admin.apache.org", cred); + Credentials got = state.getCredentials("admin.apache.org", "myhost"); + assertNotSame(cred, got); + } + + public void testRealmSpoof2() throws Exception { + HttpState state = new HttpState(); + Credentials cred = new UsernamePasswordCredentials("name", "pass"); + state.setCredentials(null, "whatever", cred); + Credentials got = state.getCredentials("nullwhatever", null); + assertNotSame(cred, got); + } }