    1   /*
    2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/test/org/apache/commons/httpclient/auth/TestDigestAuth.java,v 1.2 2004/11/07 12:31:42 olegk Exp $
    3    * $Revision: 480424 $
    4    * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
    5    * ====================================================================
    6    *
    7    *  Licensed to the Apache Software Foundation (ASF) under one or more
    8    *  contributor license agreements.  See the NOTICE file distributed with
    9    *  this work for additional information regarding copyright ownership.
    10   *  The ASF licenses this file to You under the Apache License, Version 2.0
    11   *  (the "License"); you may not use this file except in compliance with
    12   *  the License.  You may obtain a copy of the License at
    13   *
    14   *      http://www.apache.org/licenses/LICENSE-2.0
    15   *
    16   *  Unless required by applicable law or agreed to in writing, software
    17   *  distributed under the License is distributed on an "AS IS" BASIS,
    18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    19   *  See the License for the specific language governing permissions and
    20   *  limitations under the License.
    21   * ====================================================================
    22   *
    23   * This software consists of voluntary contributions made by many
    24   * individuals on behalf of the Apache Software Foundation.  For more
    25   * information on the Apache Software Foundation, please see
    26   * <http://www.apache.org/>.
    27   *
    28   */
    29
    30  package org.apache.commons.httpclient.auth;
    31
    32  import java.io.IOException;
    33  import java.util.Map;
    34
    35  import org.apache.commons.httpclient.FakeHttpMethod;
    36  import org.apache.commons.httpclient.Header;
    37  import org.apache.commons.httpclient.HttpClient;
    38  import org.apache.commons.httpclient.HttpStatus;
    39  import org.apache.commons.httpclient.HttpVersion;
    40  import org.apache.commons.httpclient.UsernamePasswordCredentials;
    41  import org.apache.commons.httpclient.protocol.Protocol;
    42  import org.apache.commons.httpclient.server.HttpService;
    43  import org.apache.commons.httpclient.server.RequestLine;
    44  import org.apache.commons.httpclient.server.SimpleHttpServer;
    45  import org.apache.commons.httpclient.server.SimpleRequest;
    46  import org.apache.commons.httpclient.server.SimpleResponse;
    47
    48  import junit.framework.Test;
    49  import junit.framework.TestCase;
    50  import junit.framework.TestSuite;
    51
    52  /**
    53   * Test Methods for DigestScheme Authentication.
    54   *
    55   * @author Rodney Waldhoff
    56   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    57   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    58   */
    59  public class TestDigestAuth extends TestCase {
    60
    61      // ------------------------------------------------------------ Constructor
    62      public TestDigestAuth(String testName) {
    63          super(testName);
    64      }
    65
    66      // ------------------------------------------------------------------- Main
    67      public static void main(String args[]) {
    68          String[] testCaseName = { TestDigestAuth.class.getName() };
    69          junit.textui.TestRunner.main(testCaseName);
    70      }
    71
    72      // ------------------------------------------------------- TestCase Methods
    73
    74      public static Test suite() {
    75          return new TestSuite(TestDigestAuth.class);
    76      }
    77
    78      public void testDigestAuthenticationWithNoRealm() throws Exception {
    79          String challenge = "Digest";
    80          try {
    81              AuthScheme authscheme = new DigestScheme();
    82              authscheme.processChallenge(challenge);
    83              fail("Should have thrown MalformedChallengeException");
    84          } catch(MalformedChallengeException e) {
    85              // expected
    86          }
    87      }
    88
    89      public void testDigestAuthenticationWithNoRealm2() throws Exception {
    90          String challenge = "Digest ";
    91          try {
    92              AuthScheme authscheme = new DigestScheme();
    93              authscheme.processChallenge(challenge);
    94              fail("Should have thrown MalformedChallengeException");
    95          } catch(MalformedChallengeException e) {
    96              // expected
    97          }
    98      }
    99
    100     public void testDigestAuthenticationWithDefaultCreds() throws Exception {
    101         String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
    102         FakeHttpMethod method = new FakeHttpMethod("/");
    103         UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
    104         AuthScheme authscheme = new DigestScheme();
    105         authscheme.processChallenge(challenge);
    106         String response = authscheme.authenticate(cred, method);
    107         Map table = AuthChallengeParser.extractParams(response);
    108         assertEquals("username", table.get("username"));
    109         assertEquals("realm1", table.get("realm"));
    110         assertEquals("/", table.get("uri"));
    111         assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
    112         assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
    113     }
    114
    115     public void testDigestAuthentication() throws Exception {
    116         String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
    117         UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
    118         FakeHttpMethod method = new FakeHttpMethod("/");
    119         AuthScheme authscheme = new DigestScheme();
    120         authscheme.processChallenge(challenge);
    121         String response = authscheme.authenticate(cred, method);
    122         Map table = AuthChallengeParser.extractParams(response);
    123         assertEquals("username", table.get("username"));
    124         assertEquals("realm1", table.get("realm"));
    125         assertEquals("/", table.get("uri"));
    126         assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
    127         assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response"));
    128     }
    129
    130     public void testDigestAuthenticationWithQueryStringInDigestURI() throws Exception {
    131         String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
    132         UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
    133         FakeHttpMethod method = new FakeHttpMethod("/");
    134         method.setQueryString("param=value");
    135         AuthScheme authscheme = new DigestScheme();
    136         authscheme.processChallenge(challenge);
    137         String response = authscheme.authenticate(cred, method);
    138         Map table = AuthChallengeParser.extractParams(response);
    139         assertEquals("username", table.get("username"));
    140         assertEquals("realm1", table.get("realm"));
    141         assertEquals("/?param=value", table.get("uri"));
    142         assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
    143         assertEquals("a847f58f5fef0bc087bcb9c3eb30e042", table.get("response"));
    144     }
    145
    146     public void testDigestAuthenticationWithMultipleRealms() throws Exception {
    147         String challenge1 = "Digest realm=\"realm1\", nonce=\"abcde\"";
    148         String challenge2 = "Digest realm=\"realm2\", nonce=\"123546\"";
    149         UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
    150         UsernamePasswordCredentials cred2 = new UsernamePasswordCredentials("uname2","password2");
    151
    152         FakeHttpMethod method = new FakeHttpMethod("/");
    153         AuthScheme authscheme1 = new DigestScheme();
    154         authscheme1.processChallenge(challenge1);
    155         String response1 = authscheme1.authenticate(cred, method);
    156         Map table = AuthChallengeParser.extractParams(response1);
    157         assertEquals("username", table.get("username"));
    158         assertEquals("realm1", table.get("realm"));
    159         assertEquals("/", table.get("uri"));
    160         assertEquals("abcde", table.get("nonce"));
    161         assertEquals("786f500303eac1478f3c2865e676ed68", table.get("response"));
    162
    163         AuthScheme authscheme2 = new DigestScheme();
    164         authscheme2.processChallenge(challenge2);
    165         String response2 = authscheme2.authenticate(cred2, method);
    166         table = AuthChallengeParser.extractParams(response2);
    167         assertEquals("uname2", table.get("username"));
    168         assertEquals("realm2", table.get("realm"));
    169         assertEquals("/", table.get("uri"));
    170         assertEquals("123546", table.get("nonce"));
    171         assertEquals("0283edd9ef06a38b378b3b74661391e9", table.get("response"));
    172     }
    173
    174     /**
    175      * Test digest authentication using the MD5-sess algorithm.
    176      */
    177     public void testDigestAuthenticationMD5Sess() throws Exception {
    178         // Example using Digest auth with MD5-sess
    179
    180         String realm="realm";
    181         String username="username";
    182         String password="password";
    183         String nonce="e273f1776275974f1a120d8b92c5b3cb";
    184
    185         String challenge="Digest realm=\"" + realm + "\", "
    186             + "nonce=\"" + nonce + "\", "
    187             + "opaque=\"SomeString\", "
    188             + "stale=false, "
    189             + "algorithm=MD5-sess, "
    190             + "qop=\"auth,auth-int\""; // we pass both but expect auth to be used
    191
    192         UsernamePasswordCredentials cred =
    193             new UsernamePasswordCredentials(username, password);
    194         FakeHttpMethod method = new FakeHttpMethod("/");
    195
    196         AuthScheme authscheme = new DigestScheme();
    197         authscheme.processChallenge(challenge);
    198         String response = authscheme.authenticate(cred, method);
    199         assertTrue(response.indexOf("nc=00000001") > 0); // test for quotes
    200         assertTrue(response.indexOf("qop=auth") > 0); // test for quotes
    201         Map table = AuthChallengeParser.extractParams(response);
    202         assertEquals(username, table.get("username"));
    203         assertEquals(realm, table.get("realm"));
    204         assertEquals("MD5-sess", table.get("algorithm"));
    205         assertEquals("/", table.get("uri"));
    206         assertEquals(nonce, table.get("nonce"));
    207         assertEquals(1, Integer.parseInt((String) table.get("nc"),16));
    208         assertTrue(null != table.get("cnonce"));
    209         assertEquals("SomeString", table.get("opaque"));
    210         assertEquals("auth", table.get("qop"));
    211         //@TODO: add better check
    212         assertTrue(null != table.get("response"));
    213     }
    214
    215     /**
    216      * Test digest authentication using the MD5-sess algorithm.
    217      */
    218     public void testDigestAuthenticationMD5SessNoQop() throws Exception {
    219         // Example using Digest auth with MD5-sess
    220
    221         String realm="realm";
    222         String username="username";
    223         String password="password";
    224         String nonce="e273f1776275974f1a120d8b92c5b3cb";
    225
    226         String challenge="Digest realm=\"" + realm + "\", "
    227             + "nonce=\"" + nonce + "\", "
    228             + "opaque=\"SomeString\", "
    229             + "stale=false, "
    230             + "algorithm=MD5-sess";
    231
    232         UsernamePasswordCredentials cred =
    233             new UsernamePasswordCredentials(username, password);
    234         FakeHttpMethod method = new FakeHttpMethod("/");
    235
    236         AuthScheme authscheme = new DigestScheme();
    237         authscheme.processChallenge(challenge);
    238         String response = authscheme.authenticate(cred, method);
    239
    240         Map table = AuthChallengeParser.extractParams(response);
    241         assertEquals(username, table.get("username"));
    242         assertEquals(realm, table.get("realm"));
    243         assertEquals("MD5-sess", table.get("algorithm"));
    244         assertEquals("/", table.get("uri"));
    245         assertEquals(nonce, table.get("nonce"));
    246         assertTrue(null == table.get("nc"));
    247         assertEquals("SomeString", table.get("opaque"));
    248         assertTrue(null == table.get("qop"));
    249         //@TODO: add better check
    250         assertTrue(null != table.get("response"));
    251     }
    252
    253     /**
    254      * Test digest authentication with invalud qop value
    255      */
    256     public void testDigestAuthenticationMD5SessInvalidQop() throws Exception {
    257         // Example using Digest auth with MD5-sess
    258
    259         String realm="realm";
    260         String username="username";
    261         String password="password";
    262         String nonce="e273f1776275974f1a120d8b92c5b3cb";
    263
    264         String challenge="Digest realm=\"" + realm + "\", "
    265             + "nonce=\"" + nonce + "\", "
    266             + "opaque=\"SomeString\", "
    267             + "stale=false, "
    268             + "algorithm=MD5-sess, "
    269             + "qop=\"jakarta\""; // jakarta is an invalid qop value
    270
    271         UsernamePasswordCredentials cred =
    272             new UsernamePasswordCredentials(username, password);
    273         try {
    274             AuthScheme authscheme = new DigestScheme();
    275             authscheme.processChallenge(challenge);
    276             fail("MalformedChallengeException exception expected due to invalid qop value");
    277         } catch(MalformedChallengeException e) {
    278         }
    279     }
    280
    281     private class StaleNonceService implements HttpService {
    282
    283         public StaleNonceService() {
    284             super();
    285         }
    286
    287         public boolean process(final SimpleRequest request, final SimpleResponse response)
    288             throws IOException
    289         {
    290             RequestLine requestLine = request.getRequestLine();
    291             HttpVersion ver = requestLine.getHttpVersion();
    292             Header auth = request.getFirstHeader("Authorization");
    293             if (auth == null) {
    294                 response.setStatusLine(ver, HttpStatus.SC_UNAUTHORIZED);
    295                 response.addHeader(new Header("WWW-Authenticate",
    296                         "Digest realm=\"realm1\", nonce=\"ABC123\""));
    297                 response.setBodyString("Authorization required");
    298                 return true;
    299             } else {
    300                 Map table = AuthChallengeParser.extractParams(auth.getValue());
    301                 String nonce = (String)table.get("nonce");
    302                 if (nonce.equals("ABC123")) {
    303                     response.setStatusLine(ver, HttpStatus.SC_UNAUTHORIZED);
    304                     response.addHeader(new Header("WWW-Authenticate",
    305                             "Digest realm=\"realm1\", nonce=\"321CBA\", stale=\"true\""));
    306                     response.setBodyString("Authorization required");
    307                     return true;
    308                 } else {
    309                     response.setStatusLine(ver, HttpStatus.SC_OK);
    310                     response.setBodyString("Authorization successful");
    311                     return true;
    312                 }
    313             }
    314         }
    315     }
    316
    317
    318     public void testDigestAuthenticationWithStaleNonce() throws Exception {
    319         // configure the server
    320         SimpleHttpServer server = new SimpleHttpServer(); // use arbitrary port
    321         server.setTestname(getName());
    322         server.setHttpService(new StaleNonceService());
    323
    324         // configure the client
    325         HttpClient client = new HttpClient();
    326         client.getHostConfiguration().setHost(
    327                 server.getLocalAddress(), server.getLocalPort(),
    328                 Protocol.getProtocol("http"));
    329
    330         client.getState().setCredentials(AuthScope.ANY,
    331                 new UsernamePasswordCredentials("username","password"));
    332
    333         FakeHttpMethod httpget = new FakeHttpMethod("/");
    334         try {
    335             client.executeMethod(httpget);
    336         } finally {
    337             httpget.releaseConnection();
    338         }
    339         assertNotNull(httpget.getStatusLine());
    340         assertEquals(HttpStatus.SC_OK, httpget.getStatusLine().getStatusCode());
    341         Map table = AuthChallengeParser.extractParams(
    342                 httpget.getRequestHeader("Authorization").getValue());
    343         assertEquals("username", table.get("username"));
    344         assertEquals("realm1", table.get("realm"));
    345         assertEquals("/", table.get("uri"));
    346         assertEquals("321CBA", table.get("nonce"));
    347         assertEquals("7f5948eefa115296e9279225041527b3", table.get("response"));
    348         server.destroy();
    349     }
    350
    351 }