rwaldhoff 01/08/08 08:29:05 Modified: httpclient/src/java/org/apache/commons/httpclient Authenticator.java HttpClient.java State.java httpclient/src/test/org/apache/commons/httpclient TestAuthenticator.java Log: Refactored the handling of Credentials in order to * support multiple basic authentication realms * store authentication Credentials as part of State rather than part of HttpClient Revision Changes Path 1.3 +61 -21 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java Index: Authenticator.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- Authenticator.java 2001/05/11 21:42:50 1.2 +++ Authenticator.java 2001/08/08 15:29:05 1.3 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v 1.2 2001/05/11 21:42:50 jericho Exp $ - * $Revision: 1.2 $ - * $Date: 2001/05/11 21:42:50 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v 1.3 2001/08/08 15:29:05 rwaldhoff Exp $ + * $Revision: 1.3 $ + * $Date: 2001/08/08 15:29:05 $ * * ==================================================================== * @@ -63,12 +63,17 @@ package org.apache.commons.httpclient; +import java.util.StringTokenizer; +import java.util.NoSuchElementException; +import org.apache.commons.httpclient.log.*; + /** * Authenticate helper. * * @author <a href="mailto:[EMAIL PROTECTED]">Remy Maucherat</a> */ public class Authenticator { + static private final Log log = LogSource.getInstance("org.apache.commons.httpclient.Authenticator"); // ----------------------------------------------------- Instance Variables @@ -89,17 +94,20 @@ * @param state State * @param credentials Credentials to use to answser the challenge * @return String response to the challenge + * @deprecated */ public static String challengeResponse(State state, Credentials credentials) throws HttpException { + log.debug("Authenticator.challengeResponse(State,Credentials)"); if (credentials == null) throw new HttpException(HttpException.NO_CREDENTIALS_GIVEN); String challenge = state.getAuthenticateToken(); - if (challenge == null) + if (challenge == null) { return null; + } int space = challenge.indexOf(' '); if (space < 0) @@ -107,42 +115,74 @@ String challengeName = challenge.substring(0, space); - if (challengeName.equalsIgnoreCase("basic")) { + if ("basic".equalsIgnoreCase(challengeName)) { return basic(state, credentials); - } else if (challengeName.equalsIgnoreCase("digest")) { - return digest(state, credentials); + } else if ("digest".equalsIgnoreCase(challengeName)) { + throw new UnsupportedOperationException("Digest authentication is not supported."); } else { + throw new UnsupportedOperationException("Authentication type \"" + challengeName + "\" is not recognized."); + } + } + + public static String challengeResponse(State state) throws HttpException { + log.debug("Authenticator.challengeResponse(State)"); + String challenge = state.getAuthenticateToken(); + if (challenge == null) { + return null; } - return null; - } + StringTokenizer toker = new StringTokenizer(challenge); + String challengeName = null; + try { + challengeName = toker.nextToken(); + } catch(NoSuchElementException e) { + return null; + } + if ("basic".equalsIgnoreCase(challengeName)) { + String realm = null; + try { + realm = toker.nextToken(); + } catch(NoSuchElementException e) { + throw new HttpException("Expected realm name in basic authentication challenge."); + } + return basic(realm,state); + } else if ("digest".equalsIgnoreCase(challengeName)) { + throw new UnsupportedOperationException("Digest authentication is not supported."); + } else { + throw new UnsupportedOperationException("Authentication type \"" + challengeName + "\" is not recognized."); + } + } /** * Generate a basic response. * * @param credentials Credentials to use to answser the challenge + * @deprecated */ public static String basic(State state, Credentials credentials) { - + log.debug("Authenticator.basic(State,Credentials)"); String authString = credentials.getUserName() + ":" + credentials.getPassword(); return "Basic " + new String(base64.encode(authString.getBytes())); } - - - /** - * Generate a basic response. - * - * @param credentials Credentials to use to answser the challenge - */ - public static String digest(State state, Credentials credentials) { - - return null; + public static String basic(String realm, State state) throws HttpException { + log.debug("Authenticator.basic(String,State)"); + Credentials cred = state.getCredentials(realm); + if(null == cred) { + if(log.isInfoEnabled()) { + log.info("No credentials found for realm \"" + realm + "\", attempting to use default credentials."); + } + cred = state.getDefaultCredentials(); + if(null == cred) { + throw new HttpException(HttpException.NO_CREDENTIALS_GIVEN); + } + } + String authString = cred.getUserName() + ":" + cred.getPassword(); + return "Basic " + new String(base64.encode(authString.getBytes())); } - } 1.25 +67 -16 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java Index: HttpClient.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v retrieving revision 1.24 retrieving revision 1.25 diff -u -r1.24 -r1.25 --- HttpClient.java 2001/08/02 23:00:58 1.24 +++ HttpClient.java 2001/08/08 15:29:05 1.25 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v 1.24 2001/08/02 23:00:58 rwaldhoff Exp $ - * $Revision: 1.24 $ - * $Date: 2001/08/02 23:00:58 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v 1.25 2001/08/08 15:29:05 rwaldhoff Exp $ + * $Revision: 1.25 $ + * $Date: 2001/08/08 15:29:05 $ * * ==================================================================== * @@ -107,7 +107,10 @@ * instead */ public HttpClient(String user, String password) { - setCredentials(new Credentials(user, password)); + if(null == state) { + state = new State(); + } + state.setDefaultCredentials(new Credentials(user, password)); } @@ -135,12 +138,10 @@ */ public HttpClient(URL url, String user, String password) { this(url); - /* - * when this constructor is finally removed, - * the endSession method should be altered - * to null out the credentials - */ - setCredentials(new Credentials(user, password)); + if(null == state) { + state = new State(); + } + state.setDefaultCredentials(new Credentials(user, password)); } @@ -245,22 +246,45 @@ /** * Set the credentials to use. + * @deprecated */ public void setCredentials(Credentials credentials) { - this.credentials = credentials; + if(null == state) { + state = new State(); + } + state.setDefaultCredentials(credentials); } + public void setCredentials(String realm, Credentials credentials) { + if(null == state) { + state = new State(); + } + state.setCredentials(realm,credentials); + } /** * Get the credentials used. + * @deprecated */ public Credentials getCredentials() { - return credentials; + if(null == state) { + return null; + } else { + return state.getDefaultCredentials(); + } } + public Credentials getCredentials(String realm) { + if(null == state) { + return null; + } else { + return state.getCredentials(realm); + } + } /** * Set debug level. + * @deprecated */ public void setDebug(int debug) { this.debug = debug; @@ -309,19 +333,47 @@ /** * Get the username. + * @deprecated */ public String getUserName() { - return (credentials != null) ? credentials.getUserName() : null; + if(null == state) { + return null; + } else { + Credentials creds = state.getDefaultCredentials(); + return null == creds ? null : creds.getUserName(); + } } + public String getUserName(String realm) { + if(null == state) { + return null; + } else { + Credentials creds = state.getCredentials(realm); + return null == creds ? null : creds.getUserName(); + } + } /** * Get the password. + * @deprecated */ public String getPassword() { - return (credentials != null) ? credentials.getPassword() : null; + if(null == state) { + return null; + } else { + Credentials creds = state.getDefaultCredentials(); + return null == creds ? null : creds.getPassword(); + } } + public String getPassword(String realm) { + if(null == state) { + return null; + } else { + Credentials creds = state.getCredentials(realm); + return null == creds ? null : creds.getPassword(); + } + } /** * Set stream interceptor. @@ -855,8 +907,7 @@ if (state.getAuthenticateToken() != null) { - String challengeResponse = Authenticator.challengeResponse - (state, credentials); + String challengeResponse = Authenticator.challengeResponse(state); if (challengeResponse != null) { String authorizationHeader = "Authorization: " + challengeResponse + "\r\n"; wireLog.info(">> \"" + authorizationHeader + "\""); 1.3 +80 -43 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/State.java Index: State.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/State.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- State.java 2001/07/22 19:28:33 1.2 +++ State.java 2001/08/08 15:29:05 1.3 @@ -1,13 +1,13 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/State.java,v 1.2 2001/07/22 19:28:33 remm Exp $ - * $Revision: 1.2 $ - * $Date: 2001/07/22 19:28:33 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/State.java,v 1.3 2001/08/08 15:29:05 rwaldhoff Exp $ + * $Revision: 1.3 $ + * $Date: 2001/08/08 15:29:05 $ * * ==================================================================== * * The Apache Software License, Version 1.1 * - * Copyright (c) 1999 The Apache Software Foundation. All rights + * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -15,7 +15,7 @@ * are met: * * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. + * 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 @@ -23,15 +23,15 @@ * distribution. * * 3. The end-user documentation included with the redistribution, if - * any, must include the following acknowlegement: - * "This product includes software developed by the + * 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 + * from this software without prior written permission. For written * permission, please contact [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache" @@ -59,38 +59,40 @@ * * [Additional notices, if required by prior licensing conditions] * - */ + */ package org.apache.commons.httpclient; import java.io.IOException; -import java.util.Hashtable; +import java.util.HashMap; import java.util.Vector; import java.util.Enumeration; /** * Session state. - * + * * @author <a href="mailto:[EMAIL PROTECTED]">Remy Maucherat</a> */ public class State { - - + + // ----------------------------------------------------- Instance Variables - - + + protected HashMap credMap = new HashMap(); + protected Credentials defaultCred = null; + /** * Authenticate token. */ protected String authenticateToken = null; - - + + /** * Cookies. */ protected Vector cookies = new Vector(); - - + + /** * URL encoding switch. */ @@ -101,8 +103,8 @@ * URL encoding charset. */ protected String URLEncodingCharset = "UTF8"; - - + + /** * URL decoding charset. */ @@ -110,22 +112,22 @@ // ------------------------------------------------------------- Properties - - + + /** * Add a cookie */ public void addCookie(Cookie cookie) { - + if (cookie != null) { - + boolean found = false; - + // let's remove the cookie if it's already saved - for (Enumeration e = cookies.elements(); + for (Enumeration e = cookies.elements(); !found && e.hasMoreElements(); ) { Cookie tmp = (Cookie) e.nextElement(); - if (cookie.getDomain().equals(tmp.getDomain()) && + if (cookie.getDomain().equals(tmp.getDomain()) && cookie.getName().equals(tmp.getName())) { found = true; cookies.removeElement(tmp); @@ -133,10 +135,10 @@ } cookies.addElement(cookie); } - + } - - + + /** * Add a number of cookies */ @@ -147,34 +149,34 @@ } } } - - + + // FIXME: this breaks encapsulation on the cookie vector public Vector getCookies() { return cookies; } - - + + /** * Authenticate token setter. - * + * * @param authenticateToken Authenticate token */ public void setAuthenticateToken(String authenticateToken) { this.authenticateToken = authenticateToken; } - - + + /** * Authenticate token accessor. - * + * * @return String authenticate token */ public String getAuthenticateToken() { return authenticateToken; } - - + + /** * Set the URL encoding flag. */ @@ -198,10 +200,45 @@ this.URLDecodingCharset = URLDecodingCharset; } + /** + * Set the {@link Credentials} for the given authentication realm. + */ + public void setCredentials(String realm, Credentials credentials) { + credMap.put(realm,credentials); + } + + + /** + * Get the {@link Credentials} for the given authentication realm. + */ + public Credentials getCredentials(String realm) { + return (Credentials)(credMap.get(realm)); + } + /** + * Set the default {@link Credentials}, used when no + * other realm is specified, or when no credential is + * found to match the given realm. + * + * @deprecated Use + * {@link #setCredentials(java.lang.String,org.apache.commons.httpclient.Credentials)} + * instead. + */ + public void setDefaultCredentials(Credentials credentials) { + defaultCred = credentials; + } + + + /** + * Get the defualt {@link Credentials}. + */ + public Credentials getDefaultCredentials() { + return defaultCred; + } + // --------------------------------------------------------- Public Methods - - + + /** * URL encode. */ 1.2 +68 -8 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java Index: TestAuthenticator.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- TestAuthenticator.java 2001/08/07 17:42:21 1.1 +++ TestAuthenticator.java 2001/08/08 15:29:05 1.2 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v 1.1 2001/08/07 17:42:21 rwaldhoff Exp $ - * $Revision: 1.1 $ - * $Date: 2001/08/07 17:42:21 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v 1.2 2001/08/08 15:29:05 rwaldhoff Exp $ + * $Revision: 1.2 $ + * $Date: 2001/08/08 15:29:05 $ * ==================================================================== * Copyright (C) The Apache Software Foundation. All rights reserved. * @@ -18,7 +18,7 @@ * Unit tests for {@link Authenticator}. * * @author Rodney Waldhoff - * @version $Id: TestAuthenticator.java,v 1.1 2001/08/07 17:42:21 rwaldhoff Exp $ + * @version $Id: TestAuthenticator.java,v 1.2 2001/08/08 15:29:05 rwaldhoff Exp $ */ public class TestAuthenticator extends TestCase { @@ -41,16 +41,76 @@ // ----------------------------------------------------------- Test Methods - public void testChallengeResponseWithNullCreds() { + public void testBasicAuthenticationWithNoCreds() { State state = new State(); + state.setAuthenticateToken("Basic realm1"); try { - Authenticator.challengeResponse(state,null); - fail("Should have thrown an HttpException"); + Authenticator.challengeResponse(state); + fail("Should have thrown HttpException"); } catch(HttpException e) { - } catch(Throwable t) { - fail("Should have thrown an HttpException, found " + t + " instead."); + // expected } } + public void testBasicAuthenticationWithNoRealm() { + State state = new State(); + state.setAuthenticateToken("Basic"); + try { + Authenticator.challengeResponse(state); + fail("Should have thrown HttpException"); + } catch(HttpException e) { + // expected + } + } + + public void testBasicAuthenticationWithNoChallenge() throws Exception { + State state = new State(); + assert(null == Authenticator.challengeResponse(state)); + } + + public void testBasicAuthenticationWithNullState() throws Exception { + try { + Authenticator.challengeResponse(null); + fail("Should have thrown NullPointerException"); + } catch(NullPointerException e) { + // expected + } + } + + public void testBasicAuthenticationWithDefaultCreds() throws Exception { + State state = new State(); + state.setAuthenticateToken("Basic realm1"); + state.setDefaultCredentials(new Credentials("username","password")); + String response = Authenticator.challengeResponse(state); + String expected = "Basic " + new String(Base64.encode("username:password".getBytes())); + assertEquals(expected,response); + } + + public void testBasicAuthentication() throws Exception { + State state = new State(); + state.setAuthenticateToken("Basic realm1"); + state.setCredentials("realm1",new Credentials("username","password")); + String response = Authenticator.challengeResponse(state); + String expected = "Basic " + new String(Base64.encode("username:password".getBytes())); + assertEquals(expected,response); + } + + public void testBasicAuthenticationWithMutlipleRealms() throws Exception { + State state = new State(); + state.setCredentials("realm1",new Credentials("username","password")); + state.setCredentials("realm2",new Credentials("uname2","password2")); + { + state.setAuthenticateToken("Basic realm1"); + String response = Authenticator.challengeResponse(state); + String expected = "Basic " + new String(Base64.encode("username:password".getBytes())); + assertEquals(expected,response); + } + { + state.setAuthenticateToken("Basic realm2"); + String response = Authenticator.challengeResponse(state); + String expected = "Basic " + new String(Base64.encode("uname2:password2".getBytes())); + assertEquals(expected,response); + } + } }