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