Take 2:

- DigestScheme requires a nonce in every challange now
  (according to RFC 2617) test cases changed accordingly
- included Olegs feedback

After reading RFC 2617 I came to the conclusion, that we need to rework out authentication mechanism to support stateful authentication retries. Digest Authentication requires you to keep track of the number of times authentication has been retries (NC) and to calculate some values only on the first time etc. This can be achieved by making the DigestScheme more stateful (a1) and by changing authenticator to keep the DigestScheme instance across retries and pass in the challenge each time.

Odi

Oleg Kalnichevski wrote:

Odi,
The patch looks fine to me. There is just a few minor points that I
would like to be considered before the patch is committed:

- DigestScheme#DigestScheme( String ) constructor should probably log a
warning message or even throw an exception if it encounters an
unrecognised 'qop' element. Currently they are just silently ignored.
- Use StringBuffer to concatenate strings in DigestScheme#createDigest(
String, String ).
- A test case for unsupported qop in HTTP Digest authentication would be
nice.
- The patch makes a few public methods private (quite appropriately in
my opinion). Nobody is going to miss them, I think, however, the fact of
2.0 API breakage should be reflected in API_CHANGES_2_1.txt

Cheers

Oleg
Index: API_CHANGES_2_1.txt
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/API_CHANGES_2_1.txt,v
retrieving revision 1.2
diff -u -r1.2 API_CHANGES_2_1.txt
--- API_CHANGES_2_1.txt 5 Jul 2003 22:59:15 -0000       1.2
+++ API_CHANGES_2_1.txt 17 Sep 2003 08:07:18 -0000
@@ -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)
Index: release_notes.txt
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/release_notes.txt,v
retrieving revision 1.13
diff -u -r1.13 release_notes.txt
--- release_notes.txt   1 Aug 2003 23:20:09 -0000       1.13
+++ release_notes.txt   17 Sep 2003 08:07:18 -0000
@@ -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 
Index: src/java/org/apache/commons/httpclient/auth/DigestScheme.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v
retrieving revision 1.8
diff -u -r1.8 DigestScheme.java
--- src/java/org/apache/commons/httpclient/auth/DigestScheme.java       11 Sep 2003 
09:09:42 -0000      1.8
+++ src/java/org/apache/commons/httpclient/auth/DigestScheme.java       17 Sep 2003 
08:07:21 -0000
@@ -1,7 +1,7 @@
 /*
- * $Header: 
/home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v
 1.8 2003/09/11 09:09:42 oglueck Exp $
- * $Revision: 1.8 $
- * $Date: 2003/09/11 09:09:42 $
+ * $Header: 
/home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v
 1.7 2003/09/08 02:00:03 mbecke Exp $
+ * $Revision: 1.7 $
+ * $Date: 2003/09/08 02:00:03 $
  *
  * ====================================================================
  *
@@ -63,7 +63,7 @@
 
 package org.apache.commons.httpclient.auth;
 
-import java.util.Map;
+import java.util.StringTokenizer;
 import java.security.MessageDigest;
 
 import org.apache.commons.httpclient.HttpConstants;
@@ -75,7 +75,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:[EMAIL PROTECTED]">Remy Maucherat</a>
  * @author Rodney Waldhoff
@@ -102,6 +107,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 +144,39 @@
     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");   
+        }
+        
+        try {
+            cnonce = createCnonce();   
+        } catch(AuthenticationException e) {
+            throw new RuntimeException(e.toString());   
+        }
     }
 
 
@@ -174,37 +220,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 
-     * [EMAIL PROTECTED] 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 +243,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 +279,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 +353,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 
Index: src/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.30
diff -u -r1.30 TestAuthenticator.java
--- src/test/org/apache/commons/httpclient/TestAuthenticator.java       11 Sep 2003 
09:09:42 -0000      1.30
+++ src/test/org/apache/commons/httpclient/TestAuthenticator.java       17 Sep 2003 
08:07:24 -0000
@@ -1,7 +1,7 @@
 /*
- * $Header: 
/home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v
 1.30 2003/09/11 09:09:42 oglueck Exp $
- * $Revision: 1.30 $
- * $Date: 2003/09/11 09:09:42 $
+ * $Header: 
/home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v
 1.29 2003/09/08 02:00:03 mbecke Exp $
+ * $Revision: 1.29 $
+ * $Date: 2003/09/08 02:00:03 $
  * ====================================================================
  *
  * The Apache Software License, Version 1.1
@@ -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;
 
@@ -76,7 +75,7 @@
  *
  * @author Rodney Waldhoff
  * @author <a href="mailto:[EMAIL PROTECTED]">Jeff Dever</a>
- * @version $Id: TestAuthenticator.java,v 1.30 2003/09/11 09:09:42 oglueck Exp $
+ * @version $Id: TestAuthenticator.java,v 1.29 2003/09/08 02:00:03 mbecke Exp $
  */
 public class TestAuthenticator extends TestNoHostBase {
 
@@ -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"

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to