commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jsde...@apache.org
Subject cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient SimpleHttpConection.java SimpleHttpMethod.java TestAuthenticator.java
Date Wed, 07 Aug 2002 19:24:17 GMT
jsdever     2002/08/07 12:24:17

  Modified:    httpclient/src/java/org/apache/commons/httpclient
                        Authenticator.java HttpMethodBase.java
               httpclient/src/test/org/apache/commons/httpclient
                        SimpleHttpConection.java SimpleHttpMethod.java
                        TestAuthenticator.java
  Log:
  Fix for: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=11498
  
  Will now correctly parse multiple challenges and pick Digest if it is present, or fall back
on Basic.  Unrecognized auth types are ignored.
  
  Also fixed a similar issue with proxy digest auth throwing a npe.  Added 4 tests for the
multiple headers case.
  
  Improved the Simple connection and method to allow for more offline tests.
  
  Revision  Changes    Path
  1.24      +97 -51    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.23
  retrieving revision 1.24
  diff -u -r1.23 -r1.24
  --- Authenticator.java	28 Jul 2002 18:08:57 -0000	1.23
  +++ Authenticator.java	7 Aug 2002 19:24:16 -0000	1.24
  @@ -67,6 +67,7 @@
   
   import java.security.MessageDigest;
   import java.util.Hashtable;
  +import java.util.StringTokenizer;
   
   /**
    * Utility methods for HTTP authorization and authentication.
  @@ -200,7 +201,7 @@
        * @see #digest
        * @see HttpMethod#addRequestHeader
        */
  -    private static boolean authenticate(HttpMethod method, HttpState state, Header challengeHeader,
String respHeader) 
  +    private static boolean authenticate(HttpMethod method, HttpState state, Header authenticateHeader,
String respHeader) 
       throws HttpException, UnsupportedOperationException {
   
           log.trace("enter Authenticator.authenticate(HttpMethod, HttpState, Header, String)");
  @@ -219,7 +220,7 @@
           boolean preemptive = ("true".equals(preemptive_str));
   
           //if there is no challenge, attempt to use preemptive authorization
  -        if (challengeHeader == null){
  +        if (authenticateHeader == null){
               if (preemptive){ 
                   log.debug("Preemptively sending default basic credentials");
                   try{
  @@ -234,55 +235,81 @@
                   return false;
               }
           }
  -        log.debug("Attempting to authenticate challenge: " + challengeHeader);
  +        log.debug("Attempting to authenticate header: " + authenticateHeader);
   
   
  -        // Get the challenge from the header
  -        String challenge = challengeHeader.getValue();
  +        // XXX: Get the challenge from the header
  +        String authenticateValue = authenticateHeader.getValue();
   
   
  -        // Parse the authentication scheme from the challenge
           // TODO: Use regular expression pattern matching to parse the challenge
  -        int space = challenge.indexOf(' ');
  -        if(space < 0) {
  -            throw new HttpException("Authentication challenge \'" + challenge + "\'does
not contain an authentication scheme");
  -        }
  -        String authScheme = challenge.substring(0, space);
  +        //FIXME: This fails if the contents of a challenge contains a ','
  +        StringTokenizer challengeTok = new StringTokenizer(authenticateValue, ",");
  +        Hashtable challengeMap = new Hashtable(7);
  +        while(challengeTok.hasMoreTokens()){
  +            
  +            // Parse the authentication scheme from the challenge
  +            String chall = challengeTok.nextToken();
  +            StringTokenizer authTok = new StringTokenizer(chall, " ");
  +            String authScheme = authTok.nextToken();
   
  -        // Parse the realm from the authentication challenge
  -        // FIXME: Note that this won't work if there is more than one realm within the
challenge
  -        if (challenge.length() < space + 1) {
  -            throw new HttpException("Unable to parse authentication challenge \"" + challenge
+ "\", expected realm");
  -        }
  -        String realmstr = challenge.substring(space+1, challenge.length());
  -        realmstr.trim();
  -        if (realmstr.length() < "realm=\"\"".length()) {
  -            throw new HttpException("Unable to parse authentication challenge \"" + challenge
+ "\", expected realm");
  +            // Store the challenge keyed on the lower case authenticaion scheme
  +            challengeMap.put(authScheme.toLowerCase(), chall);
           }
  -        String realm = realmstr.substring("realm=\"".length(), realmstr.length()-1);
  -        log.debug("Parsed realm \"" + realm + "\" from challenge \"" + challenge + "\".");
   
  -        // Check for the authentication type, and add header if necessisary
           Header requestHeader = null;
  -        if ("basic".equalsIgnoreCase(authScheme)) {  // Basic authentication
  -            requestHeader = Authenticator.basic(realm, state, respHeader);
  -
  -        } else if ("digest".equalsIgnoreCase(authScheme)) {  // Digest authentication
  +        if (challengeMap.containsKey("digest")) {
  +            String challenge = (String)challengeMap.get("digest");
  +            String realm = parseRealmFromChallenge(challenge);
               requestHeader = Authenticator.digest(realm, method, state, respHeader);
  -
  -        } else {  // unrecognized authentication
  -            throw new UnsupportedOperationException("Authentication type \"" + authScheme
+ "\" is not recognized.");
  +        } else if (challengeMap.containsKey("basic")) {
  +            String challenge = (String)challengeMap.get("basic");
  +            String realm = parseRealmFromChallenge(challenge);
  +            requestHeader = Authenticator.basic(realm, state, respHeader);
  +        } else if (challengeMap.size() == 0) {
  +            throw new HttpException("No authentication scheme found in '" +
  +                    authenticateValue);
  +        } else {
  +            throw new UnsupportedOperationException("Requested authentication scheme "
+
  +                    challengeMap.keySet() + " is unsupported");
           }
   
  +        //Add the header and return the result
           if(requestHeader != null) {  // add the header
               method.addRequestHeader(requestHeader);
               return true;
           } else {  // don't add the header
               return false;
           } 
  +
       } 
   
   
  +    /** 
  +     * Parse the realm from the authentication challenge
  +     */
  +    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 firstq = realm.indexOf('"');
  +            int lastq = realm.lastIndexOf('"');
  +            if (firstq+1 < lastq) {
  +                realm = realm.substring(firstq+1, lastq);
  +            }
  +            log.debug("Parsed realm '" + realm + "' from challenge '" + challenge + "'");
  +            return realm;
  +        } catch (Exception ex) {
  +            throw new HttpException("Failed to parse realm from challenge '" + challenge
+ "'");
  +        }
  +
  +    }
  +
  +
  +
       /**
        * Create a Basic <tt>Authorization</tt> header for the given
        * <i>realm</i> and <i>state</i> to the given <i>method</i>.
  @@ -311,7 +338,8 @@
           }
   
           if(null == cred) {
  -            throw new HttpException("No credentials available for the Basic authentication
realm \'" + realm + "\'");
  +            throw new HttpException("No credentials available for the Basic authentication
realm \'" + 
  +                    realm + "\'");
           } else {
               return new Header(respHeader, Authenticator.basic(cred));
           }
  @@ -370,7 +398,7 @@
           if(null == cred) {
               throw new HttpException("No credentials available for the Digest authentication
realm \"" + realm + "\"/");
           } else {
  -            Hashtable headers = getHTTPDigestCredentials(method);
  +            Hashtable headers = getHTTPDigestCredentials(method, proxy);
               headers.put( "cnonce", "\""+createCnonce()+"\"");
               headers.put( "nc", "00000001");
               headers.put( "uri", method.getPath() );
  @@ -567,31 +595,49 @@
       { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
   
       /**
  -     * Processes the www-authenticate HTTP header received from the server that
  +     * Processes the authenticate HTTP header received from the server that
        * requires Digest authentication.
        *
  -     * @param headers The HTTP headers.
  -     * @return The parameters from www-authenticate header as a Hashtable
  -     */
  -    private static Hashtable getHTTPDigestCredentials(HttpMethod method) {
  -        log.trace("enter Authenticator.getHTTPDigestCredentials(HttpMethod)");
  +     * @param method The HTTP method.
  +     * @param proxy true if authorizing for a proxy
  +     * @return The parameters from the authenticate header as a Hashtable
  +     *  or empty Hashtable if there is no valid authorization.
  +     *
  +     * @see #processDigestToken(String,java.util.Hashtable)
  +     * @see PROXY_AUTH
  +     * @see WWW_AUTH
  +     * @since 2.0
  +     */
  +    private static Hashtable 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 new Hashtable(0);
  +        }
  +
  +        //Hashtable of digest tokens
  +        Hashtable ht = new Hashtable(17);
   
  -        String authHeader = method.getResponseHeader("www-authenticate").getValue();
  -        Hashtable ht = new Hashtable(15);
  -        if (authHeader == null) {
  -	    return ht;
  -	}
  -        authHeader = authHeader.substring(7).trim();
  +        //parse the authenticate header
           int i = 0;
           int j = authHeader.indexOf(",");
           while(j >= 0) {
  -            processDigestToken(authHeader.substring(i,j),ht);
  +            processDigestToken(authHeader.substring(i,j), ht);
               i = j+1;
               j = authHeader.indexOf(",",i);
           }
   
  -        if (i < authHeader.length())
  -            processDigestToken(authHeader.substring(i),ht);
  +        if (i < authHeader.length()) {
  +            processDigestToken(authHeader.substring(i), ht);
  +        }
           return ht;
       }
   
  
  
  
  1.45      +6 -6      jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java
  
  Index: HttpMethodBase.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
  retrieving revision 1.44
  retrieving revision 1.45
  diff -u -r1.44 -r1.45
  --- HttpMethodBase.java	7 Aug 2002 02:13:22 -0000	1.44
  +++ HttpMethodBase.java	7 Aug 2002 19:24:16 -0000	1.45
  @@ -641,10 +641,10 @@
                           break;
                   }
               } catch (HttpException httpe) {
  -                log.warn("Exception thrown authenticating: " + httpe.getMessage());
  +                log.warn(httpe.getMessage());
                   return true; // finished request
               } catch (UnsupportedOperationException uoe) {
  -                log.warn("Exception thrown authenticating: " + uoe.getMessage());
  +                log.warn(uoe.getMessage());
                   //FIXME: should this return true?
               }
   
  
  
  
  1.2       +54 -8     jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpConection.java
  
  Index: SimpleHttpConection.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpConection.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- SimpleHttpConection.java	2 Aug 2002 11:38:12 -0000	1.1
  +++ SimpleHttpConection.java	7 Aug 2002 19:24:16 -0000	1.2
  @@ -63,24 +63,50 @@
   
   package org.apache.commons.httpclient;
   
  +import org.apache.commons.logging.Log;
  +import org.apache.commons.logging.LogFactory;
  +
   import java.io.BufferedReader;
   import java.io.ByteArrayInputStream;
   import java.io.IOException;
   import java.io.InputStream;
   import java.io.StringReader;
  +import java.util.Vector;
  +
   
   /**
  - * Hack HttpConnection to test the response header reading mechanism.
  + * For test-nohost testing purposes only.
  + *
  + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    */
   class SimpleHttpConnection extends HttpConnection {
   
  +    static Log log = LogFactory.getLog("httpclient.test");
  +
  +    int hits = 0;
  +
  +    Vector headers = new Vector();
  +    Vector bodies = new Vector();
   	BufferedReader headerReader = null;
   	ByteArrayInputStream bodyInputStream = null;
   
  -	public SimpleHttpConnection(String headers, String body) {
  +    public void addResponse(String header) {
  +        addResponse(header, "");
  +    }
  +
  +    public void addResponse(String header, String body) {
  +        headers.add(header);
  +        bodies.add(body);
  +    }
  +
  +	public SimpleHttpConnection(String header, String body) {
  +		this();
  +        headers.add(header);
  +        bodies.add(body);
  +	}
  +	
  +	public SimpleHttpConnection() {
   		super(null, -1, "localhost", 80, false);
  -		this.headerReader = new BufferedReader(new StringReader(headers));
  -		bodyInputStream = new ByteArrayInputStream(body.getBytes());
   	}
   	
   	public SimpleHttpConnection(String host, int port, boolean isSecure){
  @@ -88,6 +114,24 @@
   	}
   
   	public void open() throws IOException {
  +        if (headerReader != null) return;
  +
  +        try{
  +            log.debug("hit: " + hits);
  +            headerReader = new BufferedReader(
  +                    new StringReader((String)headers.elementAt(hits)));
  +            bodyInputStream = new ByteArrayInputStream(
  +                    ((String)bodies.elementAt(hits)).getBytes());
  +            hits++;
  +        } catch (ArrayIndexOutOfBoundsException aiofbe) {
  +            throw new IOException("SimpleHttpConnection has been opened more times " +
  +                    "than it has responses.  You might need to call addResponse().");
  +        }
  +    }
  +
  +	public void close() {
  +        headerReader = null;
  +        bodyInputStream = null;
   	}
   
   	public void write(byte[] data) 
  @@ -100,7 +144,9 @@
   
   	public String readLine()
   	throws IOException, IllegalStateException {
  -		return headerReader.readLine();
  +        String str = headerReader.readLine();
  +        log.debug("read: " + str);
  +        return str;
   	}
   
   	public InputStream getResponseInputStream(HttpMethod method) {
  
  
  
  1.3       +12 -4     jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpMethod.java
  
  Index: SimpleHttpMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpMethod.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- SimpleHttpMethod.java	6 Aug 2002 15:15:32 -0000	1.2
  +++ SimpleHttpMethod.java	7 Aug 2002 19:24:16 -0000	1.3
  @@ -63,12 +63,20 @@
   
   package org.apache.commons.httpclient;
   
  +import org.apache.commons.logging.Log;
  +import org.apache.commons.logging.LogFactory;
  +
   import java.io.IOException;
   
  -/** Simple method for testing the HttpMethodBase.
  +
  +/** 
  + * For test-nohost testing purposes only.
  + *
  + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    */
   class SimpleHttpMethod extends HttpMethodBase{
   
  +    static Log log = LogFactory.getLog("httpclient.test");
       Header header = null;
   
   	SimpleHttpMethod(){
  
  
  
  1.14      +117 -18   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.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- TestAuthenticator.java	6 Aug 2002 15:15:32 -0000	1.13
  +++ TestAuthenticator.java	7 Aug 2002 19:24:16 -0000	1.14
  @@ -6,7 +6,7 @@
    *
    * The Apache Software License, Version 1.1
    *
  - * Copyright (c) 1999 The Apache Software Foundation.  All rights
  + * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
    * reserved.
    *
    * Redistribution and use in source and binary forms, with or without
  @@ -183,8 +183,8 @@
   
       public void testBasicAuthentication() throws Exception {
           HttpState state = new HttpState();
  -        state.setCredentials("realm1",new UsernamePasswordCredentials("username","password"));
  -        HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate","Basic realm=\"realm1\""));
  +        state.setCredentials("realm",new UsernamePasswordCredentials("username","password"));
  +        HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate","Basic realm=\"realm\""));
           assertTrue(Authenticator.authenticate(method,state));
           assertTrue(null != method.getRequestHeader("Authorization"));
           String expected = "Basic " + new String(Base64.encode("username:password".getBytes()));
  @@ -251,16 +251,6 @@
           assertTrue(null == method.getRequestHeader("Authorization"));
       }
   
  -    public void testMultipleChallenge() throws Exception {
  -        HttpState state = new HttpState();
  -        HttpMethod method = new SimpleHttpMethod();
  -        //set both basic and digest response headers
  -        
  -        assertTrue(! Authenticator.authenticate(method,state));
  -    }
  -
  -
  -
       // --------------------------------- Test Methods for Digest Authentication
   
       public void testDigestAuthenticationWithNoCreds() {
  @@ -336,7 +326,7 @@
           checkAuthorization(cred, method.getName(), method.getRequestHeader("Authorization").getValue());
       }
   
  -    public void testDigestAuthenticationWithMutlipleRealms() throws Exception {
  +    public void testDigestAuthenticationWithMultipleRealms() throws Exception {
           HttpState state = new HttpState();
           UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
           state.setCredentials("realm1", cred);
  @@ -375,6 +365,115 @@
           String digest = Authenticator.createDigest(cred.getUserName(),cred.getPassword(),
table);
           assertEquals(response, digest);
       }
  +
  +
  +    // --------------------------------- Test Methods for Multiple Authentication
  +
  +    public void testMultipleChallengeBasic() throws Exception {
  +        HttpState state = new HttpState();
  +        state.setCredentials("Protected", new UsernamePasswordCredentials("name", "pass"));
  +        HttpMethod method = new SimpleHttpMethod();
  +        SimpleHttpConnection conn = new SimpleHttpConnection();
  +        conn.addResponse(
  +            "HTTP/1.1 401 Unauthorized\r\n" + 
  +            "WWW-Authenticate: NTLM\r\n" +
  +            "WWW-Authenticate: Basic realm=\"Protected\"\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                );
  +        conn.addResponse( 
  +            "HTTP/1.1 200 OK\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                ); 
  +        method.execute(state, conn);
  +        Header authHeader = method.getRequestHeader("Authorization");
  +        assertNotNull(authHeader);
  +
  +        String authValue = authHeader.getValue();
  +        assertTrue(authValue.startsWith("Basic"));
  +    }
  +
  +
  +    public void testMultipleChallengeDigest() throws Exception {
  +        HttpState state = new HttpState();
  +        state.setCredentials("Protected", new UsernamePasswordCredentials("name", "pass"));
  +        HttpMethod method = new SimpleHttpMethod();
  +        SimpleHttpConnection conn = new SimpleHttpConnection();
  +        conn.addResponse(
  +            "HTTP/1.1 401 Unauthorized\r\n" + 
  +            "WWW-Authenticate: NTLM\r\n" +
  +            "WWW-Authenticate: Digest realm=\"Protected\"\r\n" +
  +            "WWW-Authenticate: Basic realm=\"Protected\"\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                );
  +        conn.addResponse( 
  +            "HTTP/1.1 200 OK\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                ); 
  +        method.execute(state, conn);
  +        Header authHeader = method.getRequestHeader("Authorization");
  +        assertNotNull(authHeader);
  +
  +        String authValue = authHeader.getValue();
  +        assertTrue(authValue.startsWith("Digest"));
  +    }
  +
  +
  +    public void testMultipleProxyChallengeBasic() throws Exception {
  +        HttpState state = new HttpState();
  +        state.setProxyCredentials("Protected", new UsernamePasswordCredentials("name",
"pass"));
  +        HttpMethod method = new SimpleHttpMethod();
  +        SimpleHttpConnection conn = new SimpleHttpConnection();
  +        conn.addResponse(
  +            "HTTP/1.1 407 Proxy Authentication Required\r\n" + 
  +            "Proxy-Authenticate: Basic realm=\"Protected\"\r\n" +
  +            "Proxy-Authenticate: NTLM\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                );
  +        conn.addResponse( 
  +            "HTTP/1.1 200 OK\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                ); 
  +        method.execute(state, conn);
  +        Header authHeader = method.getRequestHeader("Proxy-Authorization");
  +        assertNotNull(authHeader);
  +
  +        String authValue = authHeader.getValue();
  +        assertTrue(authValue.startsWith("Basic"));
  +    }
  +
  +
  +    public void testMultipleProxyChallengeDigest() throws Exception {
  +        HttpState state = new HttpState();
  +        state.setProxyCredentials("Protected", new UsernamePasswordCredentials("name",
"pass"));
  +        HttpMethod method = new SimpleHttpMethod();
  +        SimpleHttpConnection conn = new SimpleHttpConnection();
  +        conn.addResponse(
  +            "HTTP/1.1 407 Proxy Authentication Required\r\n" + 
  +            "Proxy-Authenticate: Basic realm=\"Protected\"\r\n" +
  +            "Proxy-Authenticate: Digest realm=\"Protected\"\r\n" +
  +            "Proxy-Authenticate: NTLM\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                );
  +        conn.addResponse( 
  +            "HTTP/1.1 200 OK\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                ); 
  +        method.execute(state, conn);
  +        Header authHeader = method.getRequestHeader("Proxy-Authorization");
  +        assertNotNull(authHeader);
  +
  +        String authValue = authHeader.getValue();
  +        assertTrue(authValue.startsWith("Digest"));
  +    }
  +
   
   
   }
  
  
  

--
To unsubscribe, e-mail:   <mailto:commons-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:commons-dev-help@jakarta.apache.org>


Mime
View raw message