commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From oglu...@apache.org
Subject cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestAuthenticator.java
Date Thu, 18 Sep 2003 13:56:22 GMT
oglueck     2003/09/18 06:56:22

  Modified:    httpclient API_CHANGES_2_1.txt release_notes.txt
               httpclient/src/java/org/apache/commons/httpclient/auth
                        DigestScheme.java
               httpclient/src/test/org/apache/commons/httpclient
                        TestAuthenticator.java
  Added:       httpclient/src/java/org/apache/commons/httpclient
                        HttpClientError.java
  Log:
  Reworked DigestScheme
  - The qop Parameter is parsed correctly and not just ignored
  - Missing qop is handled correctly
  - DigestScheme is now prepared to handle the auth-int qop option
    (even though an implementation is not possible with the current design)
  - The public interface of DigestScheme is narrowed (as it is not needed by the tests any
more)
  - The test cases check the actual result rather than checking for equality after another
run through the same logic. 
     Note: This is not simple for requests that require the client to generate a cnonce. Thos
cases are still TODO.
  - DigestScheme requires a nonce in every challange now
    (according to RFC 2617) test cases changed accordingly
  
  Introducing HttpClientError
  
  Reviewed by: Oleg Kalnichevski
  
  Revision  Changes    Path
  1.3       +9 -0      jakarta-commons/httpclient/API_CHANGES_2_1.txt
  
  Index: API_CHANGES_2_1.txt
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/API_CHANGES_2_1.txt,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- API_CHANGES_2_1.txt	5 Jul 2003 22:59:15 -0000	1.2
  +++ API_CHANGES_2_1.txt	18 Sep 2003 13:56:21 -0000	1.3
  @@ -22,3 +22,12 @@
   
   * NTLM classes moved to org.apache.commons.httpclient.auth package amd made private
   
  +* DigestSheme:
  +    the following public methods were removed, as they were only public for testing
  +    - authenticate(UsernamePasswordCredentials, Map)
  +    - createDigest(String, String, Map)
  +    - createDigestHeader(String, Map, String)
  +    the following public methods were made private, as they now depend on or mutate the
  +    state of DigestScheme:
  +    - createDigest(String, String)
  +    - createDigestHeader(String, String)
  
  
  
  1.14      +4 -0      jakarta-commons/httpclient/release_notes.txt
  
  Index: release_notes.txt
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/release_notes.txt,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- release_notes.txt	1 Aug 2003 23:20:09 -0000	1.13
  +++ release_notes.txt	18 Sep 2003 13:56:21 -0000	1.14
  @@ -1,5 +1,9 @@
   Changes on the CVS trunk:
   
  + * added support for MD5-sess Digest authentication scheme
  + 
  + * improved compliance to RFC 2617
  + 
    * 10791 - Improved HTTP Version configuration and tracking.
   
    * 21880 - Content-Length & Transfer-Encoding request headers formerly set by abstract

  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClientError.java
  
  Index: HttpClientError.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClientError.java,v
1.1 2003/09/18 13:56:21 oglueck Exp $
   * $Revision: 1.1 $
   * $Date: 2003/09/18 13:56:21 $
   *
   * ====================================================================
   *
   * 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
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient;
  
  /**
   * Signals that an error has occurred.
   * 
   * @author Ortwin Glück
   * @version $Revision: 1.1 $ $Date: 2003/09/18 13:56:21 $
   * @since 2.1
   */
  public class HttpClientError extends Error {
  
      /**
       * Creates a new HttpClientError with a <tt>null</tt> detail message.
       */
      public HttpClientError() {
          super();
      }
  
      /**
       * Creates a new HttpClientError with the specified detail message.
       * @param message The error message
       */
      public HttpClientError(String message) {
          super(message);
      }
  
  }
  
  
  
  1.9       +153 -94   jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java
  
  Index: DigestScheme.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v
  retrieving revision 1.8
  retrieving revision 1.9
  diff -u -r1.8 -r1.9
  --- DigestScheme.java	11 Sep 2003 09:09:42 -0000	1.8
  +++ DigestScheme.java	18 Sep 2003 13:56:21 -0000	1.9
  @@ -63,9 +63,11 @@
   
   package org.apache.commons.httpclient.auth;
   
  -import java.util.Map;
  +import java.util.StringTokenizer;
   import java.security.MessageDigest;
  +import java.security.NoSuchAlgorithmException;
   
  +import org.apache.commons.httpclient.HttpClientError;
   import org.apache.commons.httpclient.HttpConstants;
   import org.apache.commons.httpclient.Credentials;
   import org.apache.commons.httpclient.UsernamePasswordCredentials;
  @@ -75,7 +77,12 @@
   /**
    * <p>
    * Digest authentication scheme as defined in RFC 2617.
  + * Both MD5 (default) and MD5-sess are supported.
  + * Currently only qop=auth or no qop is supported. qop=auth-int
  + * is unsupported. If auth and auth-int are provided, auth is
  + * used.
    * </p>
  + * @TODO: make class more stateful regarding repeated authentication requests
    * 
    * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
    * @author Rodney Waldhoff
  @@ -102,6 +109,15 @@
           '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 
           'e', 'f'
       };
  +    
  +    //@TODO: supply a real nonce-count, currently a server will interprete a repeated request
as a replay  
  +    private static final String NC = "00000001"; //nonce-count is always 1
  +    private static final int QOP_MISSING = 0;
  +    private static final int QOP_AUTH_INT = 1;
  +    private static final int QOP_AUTH = 2;
  +
  +    private int qopVariant = QOP_MISSING;
  +    private String cnonce;
   
       /**
        * Gets an ID based upon the realm and the nonce value.  This ensures that requests
  @@ -130,7 +146,35 @@
       public DigestScheme(final String challenge) 
         throws MalformedChallengeException {
           super(challenge);
  -        this.getParameters().put("nc", "00000001");
  +        
  +        if (getParameter("nonce") == null) {
  +            throw new MalformedChallengeException("missing nonce in challange");   
  +        }
  +        
  +        boolean unsupportedQop = false;
  +        // qop parsing
  +        String qop = getParameter("qop");
  +        if (qop != null) {
  +            StringTokenizer tok = new StringTokenizer(qop,",");
  +            while (tok.hasMoreTokens()) {
  +                String variant = tok.nextToken().trim();
  +                if (variant.equals("auth")) {
  +                    qopVariant = QOP_AUTH;
  +                    break; //that's our favourite, because auth-int is unsupported
  +                } else if (variant.equals("auth-int")) {
  +                    qopVariant = QOP_AUTH_INT;               
  +                } else {
  +                    unsupportedQop = true;
  +                    LOG.warn("Unsupported qop detected: "+ variant);   
  +                }     
  +            }
  +        }        
  +        
  +        if (unsupportedQop && (qopVariant == QOP_MISSING)) {
  +            throw new MalformedChallengeException("None of the qop methods is supported");
  
  +        }
  +        
  +        cnonce = createCnonce();   
       }
   
   
  @@ -174,37 +218,13 @@
                "Credentials cannot be used for digest authentication: " 
                 + credentials.getClass().getName());
           }
  -        this.getParameters().put("cnonce", createCnonce());
           this.getParameters().put("methodname", method);
           this.getParameters().put("uri", uri);
  -        return DigestScheme.authenticate(usernamepassword, getParameters());
  -    }
  +        String digest = createDigest(usernamepassword.getUserName(),
  +                usernamepassword.getPassword());
   
  -    /**
  -     * 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: <code>uri</code>, <code>realm</code>,

  -     *  <code>nonce</code>, <code>nc</code>, <code>cnonce</code>,

  -     *  <code>qop</code>, <code>methodname</code>.
  -     * 
  -     * @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);
  +        return "Digest " + createDigestHeader(usernamepassword.getUserName(),
  +                             digest);
       }
   
       /**
  @@ -221,30 +241,30 @@
        *         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 {
  +    private String createDigest(String uname, String pwd) 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");
  -        String algorithm = (String) params.get("algorithm");
  +        String uri = getParameter("uri");
  +        String realm = getParameter("realm");
  +        String nonce = getParameter("nonce");
  +        String qop = getParameter("qop");
  +        String method = getParameter("methodname");
  +        String algorithm = getParameter("algorithm");
   
           // If an algorithm is not specified, default to MD5.
           if(algorithm == null) {
               algorithm="MD5";
           }
   
  -        if (qop != null) {
  -            qop = "auth";
  +
  +        if (qopVariant == QOP_AUTH_INT) {
  +            LOG.warn("qop=auth-int is not supported");
  +            throw new AuthenticationException(
  +                "Unsupported qop in HTTP Digest authentication");   
           }
   
           MessageDigest md5Helper;
  @@ -257,38 +277,73 @@
                  + digAlg);
           }
   
  -        // Calculating digest according to rfc 2617
  -
  -        String a1 = null;
  -        if(algorithm.equals("MD5")) {
  -            // unq(username-value) ":" unq(realm-value) ":" passwd
  -            a1 = uname + ":" + realm + ":" + pwd;
  -        } else if(algorithm.equals("MD5-sess")) {
  +        // 3.2.2.2: Calculating digest
  +        StringBuffer tmp = new StringBuffer(uname.length() + realm.length() + pwd.length()
+ 2);
  +        tmp.append(uname);
  +        tmp.append(':');
  +        tmp.append(realm);
  +        tmp.append(':');
  +        tmp.append(pwd);
  +        // unq(username-value) ":" unq(realm-value) ":" passwd
  +        String a1 = tmp.toString();
  +        //a1 is suitable for MD5 algorithm
  +        if(algorithm.equals("MD5-sess")) {
               // H( unq(username-value) ":" unq(realm-value) ":" passwd )
               //      ":" unq(nonce-value)
               //      ":" unq(cnonce-value)
   
  -            String tmp=encode(md5Helper.digest(HttpConstants.getBytes(
  -                uname + ":" + realm + ":" + pwd)));
  -
  -            a1 = tmp + ":" + nonce + ":" + cnonce;
  -        } else {
  +            String tmp2=encode(md5Helper.digest(HttpConstants.getBytes(
  +                a1)));
  +            StringBuffer tmp3 = new StringBuffer(tmp2.length() + nonce.length() + cnonce.length()
+ 2);
  +            tmp3.append(tmp2);
  +            tmp3.append(':');
  +            tmp3.append(nonce);
  +            tmp3.append(':');
  +            tmp3.append(cnonce);
  +            a1 = tmp3.toString();
  +        } else if(!algorithm.equals("MD5")) {
               LOG.warn("Unhandled algorithm " + algorithm + " requested");
  -            a1 = uname + ":" + realm + ":" + pwd;
           }
           String md5a1 = encode(md5Helper.digest(HttpConstants.getBytes(a1)));
  -        String serverDigestValue;
   
  -        String a2 = method + ":" + uri;
  +        String a2 = null;
  +        if (qopVariant == QOP_AUTH_INT) {
  +            LOG.error("Unhandled qop auth-int");
  +            //we do not have access to the entity-body or its hash
  +            //@TODO: add Method ":" digest-uri-value ":" H(entity-body)      
  +        } else {
  +            a2 = method + ":" + uri;
  +        }
           String md5a2 = encode(md5Helper.digest(HttpConstants.getBytes(a2)));
   
  -        if (qop == null) {
  +        // 3.2.2.1
  +        String serverDigestValue;
  +        if (qopVariant == QOP_MISSING) {
               LOG.debug("Using null qop method");
  -            serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2;
  +            StringBuffer tmp2 = new StringBuffer(md5a1.length() + nonce.length() + md5a2.length());
  +            tmp2.append(md5a1);
  +            tmp2.append(':');
  +            tmp2.append(nonce);
  +            tmp2.append(':');
  +            tmp2.append(md5a2);
  +            serverDigestValue = tmp2.toString();
           } else {
               LOG.debug("Using qop method " + qop);
  -            serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" + cnonce
  -                                + ":" + qop + ":" + md5a2;
  +            String qopOption = getQopVariantString();
  +            StringBuffer tmp2 = new StringBuffer(md5a1.length() + nonce.length()
  +                + NC.length() + cnonce.length() + qopOption.length() + md5a2.length() +
5);
  +            tmp2.append(md5a1);
  +            tmp2.append(':');
  +            tmp2.append(nonce);
  +            tmp2.append(':');
  +            tmp2.append(NC);
  +            tmp2.append(':');
  +            tmp2.append(cnonce);
  +            tmp2.append(':');
  +            tmp2.append(qopOption);
  +            tmp2.append(':');
  +            tmp2.append(md5a2); 
  +            serverDigestValue = tmp2.toString();
           }
   
           String serverDigest =
  @@ -296,53 +351,57 @@
   
           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: <code>uri</code>, 
  -     *  <code>realm</code>, <code>nonce</code>, <code>nc</code>,

  -     *  <code>cnonce</code>, <code>qop</code>, <code>methodname</code>.
        * @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) {
  +    private String createDigestHeader(String uname, String digest) throws AuthenticationException
{
   
           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 uri = getParameter("uri");
  +        String realm = getParameter("realm");
  +        String nonce = getParameter("nonce");
  +        String nc = getParameter("nc");
  +        String opaque = getParameter("opaque");
           String response = digest;
  -        String qop = (String) params.get("qop");
  -        String algorithm = (String) params.get("algorithm");
  -
  -        if (qop != null) {
  -            qop = "auth"; //we only support auth
  -        }
  +        String qop = getParameter("qop");
  +        String algorithm = getParameter("algorithm");
   
           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 + "\"");
  -
  +          .append(", response=\"" + response + "\"");
  +        if (qopVariant != QOP_MISSING) {
  +            sb.append(", qop=\"" + getQopVariantString() + "\"")
  +              .append(", nc="+ NC)
  +              .append(", cnonce=\"" + cnonce + "\"");
  +        }
  +        if (algorithm != null) {
  +            sb.append(", algorithm=\"" + algorithm + "\"");
  +        }    
  +        if (opaque != null) {
  +            sb.append(", opaque=\"" + opaque + "\"");
  +        }
           return sb.toString();
       }
   
  +    private String getQopVariantString() {
  +        String qopOption;
  +        if (qopVariant == QOP_AUTH_INT) {
  +            qopOption = "auth-int";   
  +        } else {
  +            qopOption = "auth";
  +        }
  +        return qopOption;            
  +    }
   
       /**
        * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long 
  @@ -374,9 +433,9 @@
        * Creates a random cnonce value based on the current time.
        * 
        * @return The cnonce value as String.
  -     * @throws AuthenticationException if MD5 algorithm is not supported.
  +     * @throws HttpClientError if MD5 algorithm is not supported.
        */
  -    public static String createCnonce() throws AuthenticationException {
  +    public static String createCnonce() {
           LOG.trace("enter DigestScheme.createCnonce()");
   
           String cnonce;
  @@ -385,8 +444,8 @@
   
           try {
               md5Helper = MessageDigest.getInstance(digAlg);
  -        } catch (Exception e) {
  -            throw new AuthenticationException(
  +        } catch (NoSuchAlgorithmException e) {
  +            throw new HttpClientError(
                 "Unsupported algorithm in HTTP Digest authentication: "
                  + digAlg);
           }
  
  
  
  1.31      +130 -47   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.30
  retrieving revision 1.31
  diff -u -r1.30 -r1.31
  --- TestAuthenticator.java	11 Sep 2003 09:09:42 -0000	1.30
  +++ TestAuthenticator.java	18 Sep 2003 13:56:22 -0000	1.31
  @@ -63,11 +63,10 @@
   package org.apache.commons.httpclient;
   
   import junit.framework.Test;
  -import junit.framework.TestCase;
   import junit.framework.TestSuite;
   
  -import java.util.Hashtable;
  -import java.util.StringTokenizer;
  +import java.util.Map;
  +
   import org.apache.commons.httpclient.auth.*;
   import org.apache.commons.httpclient.util.Base64;
   
  @@ -91,29 +90,6 @@
           junit.textui.TestRunner.main(testCaseName);
       }
   
  -    // ------------------------------------------------------- Utility Methods
  -
  -    private void checkAuthorization(UsernamePasswordCredentials cred, String methodName,
String auth) throws Exception {
  -        Hashtable table = new Hashtable();
  -        StringTokenizer tokenizer = new StringTokenizer(auth, ",=\"");
  -        while(tokenizer.hasMoreTokens()){
  -            String key = null;
  -            String value = null;
  -            if(tokenizer.hasMoreTokens())
  -                key = tokenizer.nextToken();
  -            if(tokenizer.hasMoreTokens())
  -                value = tokenizer.nextToken();
  -            if(key != null && value != null){
  -                table.put(key.trim(),value.trim());
  -            }
  -        }
  -        String response = (String) table.get("response");
  -        table.put( "methodname", methodName );
  -        String digest = DigestScheme.createDigest(cred.getUserName(),cred.getPassword(),
table);
  -        assertEquals(response, digest);
  -    }
  -
  -
       // ------------------------------------------------------- TestCase Methods
   
       public static Test suite() {
  @@ -308,7 +284,7 @@
       }
   
       public void testDigestAuthenticationWithNullHttpState() throws Exception {
  -        String challenge = "Digest realm=\"realm1\"";
  +        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
           HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate", challenge));
           try {
               AuthScheme authscheme = new DigestScheme(challenge);
  @@ -320,7 +296,7 @@
       }
   
       public void testDigestAuthenticationCaseInsensitivity() throws Exception {
  -        String challenge = "dIgEsT ReAlM=\"realm1\"";
  +        String challenge = "dIgEsT ReAlM=\"realm1\", nOnCE=\"f2a3F18799759D4f1a1C068b92b573cB\"";
           HttpState state = new HttpState();
           UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
           state.setCredentials(null, null, cred);
  @@ -332,7 +308,7 @@
   
   
       public void testDigestAuthenticationWithDefaultCreds() throws Exception {
  -        String challenge = "Digest realm=\"realm1\"";
  +        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
           HttpState state = new HttpState();
           UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
           state.setCredentials(null, null, cred);
  @@ -340,11 +316,16 @@
           AuthScheme authscheme = new DigestScheme(challenge);
           assertTrue(HttpAuthenticator.authenticate(authscheme, method, null, state));
           assertTrue(null != method.getRequestHeader("Authorization"));
  -        checkAuthorization(cred, method.getName(), method.getRequestHeader("Authorization").getValue());
  +        Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue());
  +        assertEquals("username", table.get("username"));
  +        assertEquals("realm1", table.get("realm"));
  +        assertEquals("/", table.get("uri"));
  +        assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
  +        assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
       }
   
       public void testDigestAuthentication() throws Exception {
  -        String challenge = "Digest realm=\"realm1\"";
  +        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
           HttpState state = new HttpState();
           UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
           state.setCredentials(null, null, cred);
  @@ -352,7 +333,12 @@
           AuthScheme authscheme = new DigestScheme(challenge);
           assertTrue(HttpAuthenticator.authenticate(authscheme, method, null, state));
           assertTrue(null != method.getRequestHeader("Authorization"));
  -        checkAuthorization(cred, method.getName(), method.getRequestHeader("Authorization").getValue());
  +        Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue());
  +        assertEquals("username", table.get("username"));
  +        assertEquals("realm1", table.get("realm"));
  +        assertEquals("/", table.get("uri"));
  +        assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
  +        assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
       }
   
       public void testDigestAuthenticationWithStaleNonce() throws Exception {
  @@ -386,12 +372,17 @@
           SimpleHttpMethod method = new SimpleHttpMethod();
           method.setDoAuthentication(true);
           assertEquals("Authentication failed", 200, client.executeMethod(method));
  -        checkAuthorization(cred, method.getName(), method.getRequestHeader("Authorization").getValue());
  +        Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue());
  +        assertEquals("username", table.get("username"));
  +        assertEquals("realm1", table.get("realm"));
  +        assertEquals("/", table.get("uri"));
  +        assertEquals("321CBA", table.get("nonce"));
  +        assertEquals("7f5948eefa115296e9279225041527b3", table.get("response"));
       }
   
       public void testDigestAuthenticationWithMultipleRealms() throws Exception {
  -        String challenge1 = "Digest realm=\"realm1\"";
  -        String challenge2 = "Digest realm=\"realm2\"";
  +        String challenge1 = "Digest realm=\"realm1\", nonce=\"abcde\"";
  +        String challenge2 = "Digest realm=\"realm2\", nonce=\"123546\"";
           HttpState state = new HttpState();
           UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
           state.setCredentials("realm1", null, cred);
  @@ -403,13 +394,23 @@
               HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate",challenge1));
               assertTrue(HttpAuthenticator.authenticate(authscheme1, method, null, state));
               assertTrue(null != method.getRequestHeader("Authorization"));
  -            checkAuthorization(cred, method.getName(), method.getRequestHeader("Authorization").getValue());
  +            Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue());
  +            assertEquals("username", table.get("username"));
  +            assertEquals("realm1", table.get("realm"));
  +            assertEquals("/", table.get("uri"));
  +            assertEquals("abcde", table.get("nonce"));
  +            assertEquals("786f500303eac1478f3c2865e676ed68", table.get("response"));
           }
           {
               HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate",challenge2));
               assertTrue(HttpAuthenticator.authenticate(authscheme2, method, null, state));
               assertTrue(null != method.getRequestHeader("Authorization"));
  -            checkAuthorization(cred2, method.getName(), method.getRequestHeader("Authorization").getValue());
  +            Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue());
  +            assertEquals("uname2", table.get("username"));
  +            assertEquals("realm2", table.get("realm"));
  +            assertEquals("/", table.get("uri"));
  +            assertEquals("123546", table.get("nonce"));
  +            assertEquals("0283edd9ef06a38b378b3b74661391e9", table.get("response"));
           }
       }
   
  @@ -425,11 +426,11 @@
           String nonce="e273f1776275974f1a120d8b92c5b3cb";
   
           String challenge="Digest realm=\"" + realm + "\", "
  -            + nonce + "\"" + nonce + "\", "
  +            + "nonce=\"" + nonce + "\", "
               + "opaque=\"SomeString\", "
               + "stale=false, "
               + "algorithm=MD5-sess, "
  -            + "qop=\"auth\"";
  +            + "qop=\"auth,auth-int\""; // we pass both but expect auth to be used
   
           HttpState state = new HttpState();
           UsernamePasswordCredentials cred =
  @@ -441,9 +442,91 @@
           assertTrue(HttpAuthenticator.authenticate(
               authscheme, method, null, state));
           assertTrue(null != method.getRequestHeader("Authorization"));
  -        checkAuthorization(cred, method.getName(),
  -            method.getRequestHeader("Authorization").getValue());
  +        Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue());
  +        assertEquals(username, table.get("username"));
  +        assertEquals(realm, table.get("realm"));
  +        assertEquals("MD5-sess", table.get("algorithm"));
  +        assertEquals("/", table.get("uri"));
  +        assertEquals(nonce, table.get("nonce"));
  +        assertEquals(1, Integer.parseInt((String) table.get("nc"),16));
  +        assertTrue(null != table.get("cnonce"));
  +        assertEquals("SomeString", table.get("opaque"));
  +        assertEquals("auth", table.get("qop"));
  +        //@TODO: add better check
  +        assertTrue(null != table.get("response")); 
       }
  +    
  +    /** 
  +     * Test digest authentication using the MD5-sess algorithm.
  +     */
  +    public void testDigestAuthenticationMD5SessNoQop() throws Exception {
  +        // Example using Digest auth with MD5-sess
  +
  +        String realm="realm";
  +        String username="username";
  +        String password="password";
  +        String nonce="e273f1776275974f1a120d8b92c5b3cb";
  +
  +        String challenge="Digest realm=\"" + realm + "\", "
  +            + "nonce=\"" + nonce + "\", "
  +            + "opaque=\"SomeString\", "
  +            + "stale=false, "
  +            + "algorithm=MD5-sess";
  +
  +        HttpState state = new HttpState();
  +        UsernamePasswordCredentials cred =
  +            new UsernamePasswordCredentials(username, password);
  +        state.setCredentials(realm, null, cred);
  +        AuthScheme authscheme = new DigestScheme(challenge);
  +        HttpMethod method =
  +            new SimpleHttpMethod(new Header("WWW-Authenticate", challenge));
  +        assertTrue(HttpAuthenticator.authenticate(
  +            authscheme, method, null, state));
  +        assertTrue(null != method.getRequestHeader("Authorization"));
  +        Map table = AuthChallengeParser.extractParams(method.getRequestHeader("Authorization").getValue());
  +        assertEquals(username, table.get("username"));
  +        assertEquals(realm, table.get("realm"));
  +        assertEquals("MD5-sess", table.get("algorithm"));
  +        assertEquals("/", table.get("uri"));
  +        assertEquals(nonce, table.get("nonce"));
  +        assertTrue(null == table.get("nc"));
  +        assertEquals("SomeString", table.get("opaque"));
  +        assertTrue(null == table.get("qop"));
  +        //@TODO: add better check
  +        assertTrue(null != table.get("response")); 
  +    }
  +    
  +    /** 
  +     * Test digest authentication with invalud qop value
  +     */
  +    public void testDigestAuthenticationMD5SessInvalidQop() throws Exception {
  +        // Example using Digest auth with MD5-sess
  +
  +        String realm="realm";
  +        String username="username";
  +        String password="password";
  +        String nonce="e273f1776275974f1a120d8b92c5b3cb";
  +
  +        String challenge="Digest realm=\"" + realm + "\", "
  +            + "nonce=\"" + nonce + "\", "
  +            + "opaque=\"SomeString\", "
  +            + "stale=false, "
  +            + "algorithm=MD5-sess, "
  +            + "qop=\"jakarta\""; // jakarta is an invalid qop value
  +
  +        HttpState state = new HttpState();
  +        UsernamePasswordCredentials cred =
  +            new UsernamePasswordCredentials(username, password);
  +        state.setCredentials(realm, null, cred);
  +        try {
  +            AuthScheme authscheme = new DigestScheme(challenge);
  +            fail("MalformedChallengeException exception expected due to invalid qop value");
  +        } catch(MalformedChallengeException e) {
  +            /* expected */
  +        }
  +    }    
  +
  +    
   
       // --------------------------------- Test Methods for NTLM Authentication
   
  @@ -671,7 +754,7 @@
           conn.addResponse(
               "HTTP/1.1 401 Unauthorized\r\n" + 
               "WWW-Authenticate: Unsupported\r\n" +
  -            "WWW-Authenticate: Digest realm=\"Protected\"\r\n" +
  +            "WWW-Authenticate: Digest realm=\"Protected\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"\r\n"
+
               "WWW-Authenticate: Basic realm=\"Protected\"\r\n" +
               "Connection: close\r\n" +
               "Server: HttpClient Test/2.0\r\n"
  @@ -695,7 +778,7 @@
           HttpMethod method = new SimpleHttpMethod();
           conn.addResponse(
               "HTTP/1.1 407 Proxy Authentication Required\r\n" + 
  -            "Proxy-Authenticate: Basic realm=\"Protected\"\r\n" +
  +            "Proxy-Authenticate: Basic realm=\"Protected\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"\r\n"
+
               "Proxy-Authenticate: Unsupported\r\n" +
               "Connection: close\r\n" +
               "Server: HttpClient Test/2.0\r\n"
  @@ -720,7 +803,7 @@
           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: Digest realm=\"Protected\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"\r\n"
+
               "Proxy-Authenticate: Unsupported\r\n" +
               "Connection: close\r\n" +
               "Server: HttpClient Test/2.0\r\n"
  
  
  

Mime
View raw message