View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import static org.junit.Assert.fail;
18  
19  import java.io.StringWriter;
20  import java.net.URL;
21  
22  import org.apache.commons.lang3.StringUtils;
23  import org.apache.logging.log4j.Level;
24  import org.apache.logging.log4j.LogManager;
25  import org.apache.logging.log4j.core.Logger;
26  import org.apache.logging.log4j.core.appender.WriterAppender;
27  import org.apache.logging.log4j.core.config.Configurator;
28  import org.apache.logging.log4j.core.layout.PatternLayout;
29  import org.htmlunit.html.HtmlPage;
30  import org.htmlunit.junit.BrowserRunner;
31  import org.htmlunit.junit.annotation.Alerts;
32  import org.junit.Test;
33  import org.junit.runner.RunWith;
34  
35  /**
36   * Tests for {@link DefaultCredentialsProvider}.
37   *
38   * @author Ahmed Ashour
39   * @author Ronald Brill
40   */
41  @RunWith(BrowserRunner.class)
42  public class DefaultCredentialsProvider2Test extends WebServerTestCase {
43  
44      /**
45       * {@inheritDoc}
46       */
47      @Override
48      protected boolean isBasicAuthentication() {
49          return true;
50      }
51  
52      /**
53       * @throws Exception if an error occurs
54       */
55      @Test
56      public void basicAuthenticationWrongUserName() throws Exception {
57          getMockWebConnection().setResponse(URL_SECOND, "Hello World");
58  
59          // wrong user name
60          getWebClient().getCredentialsProvider().clear();
61          ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
62                                          .addCredentials("joe", "jetty".toCharArray());
63  
64          try {
65              loadPage("Hi There");
66              fail("Should not be authorized");
67          }
68          catch (final FailingHttpStatusCodeException e) {
69              //success
70          }
71      }
72  
73      /**
74       * @throws Exception if an error occurs
75       */
76      @Test
77      public void basicAuthenticationWrongPassword() throws Exception {
78          getMockWebConnection().setResponse(URL_SECOND, "Hello World");
79  
80          // wrong user name
81          getWebClient().getCredentialsProvider().clear();
82          ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
83                                          .addCredentials("jetty", "secret".toCharArray());
84  
85          try {
86              loadPage("Hi There");
87              fail("Should not be authorized");
88          }
89          catch (final FailingHttpStatusCodeException e) {
90              //success
91          }
92      }
93  
94      /**
95       * Tests that on calling the website twice, only the first time unauthorized response is returned.
96       *
97       * @throws Exception if an error occurs
98       */
99      @Test
100     public void basicAuthentication_singleAuthenticaiton() throws Exception {
101         final Logger logger = (Logger) LogManager.getLogger("org.apache.http.headers");
102         final Level oldLevel = logger.getLevel();
103         Configurator.setLevel(logger.getName(), Level.DEBUG);
104 
105         final StringWriter stringWriter = new StringWriter();
106         final PatternLayout layout = PatternLayout.newBuilder().withPattern("%msg%n").build();
107 
108         final WriterAppender writerAppender = WriterAppender.newBuilder().setName("writeLogger").setTarget(stringWriter)
109                 .setLayout(layout).build();
110         writerAppender.start();
111 
112         logger.addAppender(writerAppender);
113         try {
114             ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
115                                             .addCredentials("jetty", "jetty".toCharArray());
116 
117             loadPage("Hi There");
118             int unauthorizedCount = StringUtils.countMatches(stringWriter.toString(), "HTTP/1.1 401");
119             assertEquals(1, unauthorizedCount);
120 
121             // and again
122             loadPage("Hi There");
123             unauthorizedCount = StringUtils.countMatches(stringWriter.toString(), "HTTP/1.1 401");
124             assertEquals(1, unauthorizedCount);
125         }
126         finally {
127             logger.removeAppender(writerAppender);
128             Configurator.setLevel(logger.getName(), oldLevel);
129         }
130     }
131 
132     /**
133      * @throws Exception if an error occurs
134      */
135     @Test
136     @Alerts("SecRet")
137     public void basicAuthenticationUserFromUrl() throws Exception {
138         final String html = DOCTYPE_HTML + "<html><body onload='alert(\"SecRet\")'></body></html>";
139         getMockWebConnection().setDefaultResponse(html);
140 
141         getWebClient().getCredentialsProvider().clear();
142 
143         try {
144             loadPage(html, URL_FIRST);
145             fail("Should not be authorized");
146         }
147         catch (final FailingHttpStatusCodeException e) {
148             //success
149         }
150 
151         final boolean urlWithCredentials = true;
152 
153         try {
154             //  now a url with credentials
155             final URL url = new URL("http://jetty:jetty@localhost:" + PORT + "/");
156             loadPageWithAlerts(html, url);
157             if (!urlWithCredentials) {
158                 fail("Should not be authorized");
159             }
160         }
161         catch (final FailingHttpStatusCodeException e) {
162             if (urlWithCredentials) {
163                 throw e;
164             }
165         }
166 
167         try {
168             // next step without credentials but the credentials are still known
169             loadPageWithAlerts(html, URL_FIRST);
170             if (!urlWithCredentials) {
171                 fail("Should not be authorized");
172             }
173         }
174         catch (final FailingHttpStatusCodeException e) {
175             if (urlWithCredentials) {
176                 throw e;
177             }
178         }
179     }
180 
181     /**
182      * @throws Exception if an error occurs
183      */
184     @Test
185     @Alerts("SecRet")
186     public void basicAuthenticationUserFromUrlUsedForNextSteps() throws Exception {
187         final String html = DOCTYPE_HTML + "<html><body onload='alert(\"SecRet\")'></body></html>";
188         getMockWebConnection().setDefaultResponse(html);
189 
190         getWebClient().getCredentialsProvider().clear();
191 
192         try {
193             // no credentials
194             loadPage(html, URL_FIRST);
195             fail("Should not be authorized");
196         }
197         catch (final FailingHttpStatusCodeException e) {
198             //success
199         }
200 
201         final boolean urlWithCredentials = true;
202 
203         try {
204             // now a url with credentials
205             final URL url = new URL("http://jetty:jetty@localhost:" + PORT + "/");
206             loadPageWithAlerts(url);
207             if (!urlWithCredentials) {
208                 fail("Should not be authorized");
209             }
210         }
211         catch (final FailingHttpStatusCodeException e) {
212             if (urlWithCredentials) {
213                 throw e;
214             }
215         }
216 
217         try {
218             // next step without credentials but the credentials are still known
219             loadPageWithAlerts(URL_FIRST);
220             if (!urlWithCredentials) {
221                 fail("Should not be authorized");
222             }
223         }
224         catch (final FailingHttpStatusCodeException e) {
225             if (urlWithCredentials) {
226                 throw e;
227             }
228         }
229 
230         try {
231             // different path
232             final URL url = new URL(URL_FIRST, "somewhere");
233             loadPageWithAlerts(url);
234             if (!urlWithCredentials) {
235                 fail("Should not be authorized");
236             }
237         }
238         catch (final FailingHttpStatusCodeException e) {
239             if (urlWithCredentials) {
240                 throw e;
241             }
242         }
243     }
244 
245     /**
246      * @throws Exception if an error occurs
247      */
248     @Test
249     @Alerts("SecRet")
250     public void basicAuthenticationUserFromUrlOverwrite() throws Exception {
251         final String html = DOCTYPE_HTML + "<html><body onload='alert(\"SecRet\")'></body></html>";
252         getMockWebConnection().setDefaultResponse(html);
253 
254         getWebClient().getCredentialsProvider().clear();
255 
256         try {
257             // no credentials
258             loadPage(html, URL_FIRST);
259             fail("Should not be authorized");
260         }
261         catch (final FailingHttpStatusCodeException e) {
262             //success
263         }
264 
265         final boolean urlWithCredentials = true;
266 
267         try {
268             // now a url with credentials
269             final URL url = new URL("http://jetty:jetty@localhost:" + PORT + "/");
270             loadPageWithAlerts(url);
271             if (!urlWithCredentials) {
272                 fail("Should not be authorized");
273             }
274         }
275         catch (final FailingHttpStatusCodeException e) {
276             if (urlWithCredentials) {
277                 throw e;
278             }
279         }
280 
281         try {
282             // next step without credentials but the credentials are still known
283             loadPageWithAlerts(URL_FIRST);
284             if (!urlWithCredentials) {
285                 fail("Should not be authorized");
286             }
287         }
288         catch (final FailingHttpStatusCodeException e) {
289             if (urlWithCredentials) {
290                 throw e;
291             }
292         }
293 
294         try {
295             final URL url = new URL("http://jetty:wrong@localhost:" + PORT + "/");
296             loadPage(html, url);
297             fail("Should not be authorized");
298         }
299         catch (final FailingHttpStatusCodeException e) {
300             //success
301         }
302     }
303 
304     /**
305      * @throws Exception if an error occurs
306      */
307     @Test
308     @Alerts("SecRet")
309     public void basicAuthenticationUserFromUrlOverwriteDefaultCredentials() throws Exception {
310         final String html = DOCTYPE_HTML + "<html><body onload='alert(\"SecRet\")'></body></html>";
311         getMockWebConnection().setDefaultResponse(html);
312 
313         getWebClient().getCredentialsProvider().clear();
314         ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
315                                         .addCredentials("jetty", "jetty".toCharArray());
316 
317         // use default credentials
318         loadPageWithAlerts(URL_FIRST);
319 
320         try {
321             final URL url = new URL("http://joe:jetty@localhost:" + PORT + "/");
322             final HtmlPage page = loadPage(html, url);
323             fail("Should not be authorized");
324         }
325         catch (final FailingHttpStatusCodeException e) {
326             //success
327         }
328     }
329 
330     /**
331      * @throws Exception if an error occurs
332      */
333     @Test
334     @Alerts("SecRet")
335     public void basicAuthenticationUserFromUrlOverwriteWrongDefaultCredentials() throws Exception {
336         final String html = DOCTYPE_HTML + "<html><body onload='alert(\"SecRet\")'></body></html>";
337         getMockWebConnection().setDefaultResponse(html);
338 
339         getWebClient().getCredentialsProvider().clear();
340         ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
341                                         .addCredentials("joe", "hack".toCharArray());
342 
343         // use default wrong credentials
344         try {
345             loadPage(html, URL_FIRST);
346             fail("Should not be authorized");
347         }
348         catch (final FailingHttpStatusCodeException e) {
349             //success
350         }
351 
352         final boolean urlWithCredentials = true;
353 
354         try {
355             // now a url with correct credentials
356             final URL url = new URL("http://jetty:jetty@localhost:" + PORT + "/");
357             loadPageWithAlerts(url);
358             if (!urlWithCredentials) {
359                 fail("Should not be authorized");
360             }
361         }
362         catch (final FailingHttpStatusCodeException e) {
363             if (urlWithCredentials) {
364                 throw e;
365             }
366         }
367     }
368 
369     /**
370      * @throws Exception if an error occurs
371      */
372     @Test
373     @Alerts("Hello World")
374     public void basicAuthenticationXHR() throws Exception {
375         final String html = DOCTYPE_HTML
376             + "<html><head><script>\n"
377             + "var xhr = new XMLHttpRequest();\n"
378             + "var handler = function() {\n"
379             + "  if (xhr.readyState == 4)\n"
380             + "    alert(xhr.responseText);\n"
381             + "}\n"
382             + "xhr.onreadystatechange = handler;\n"
383             + "xhr.open('GET', '" + URL_SECOND + "', true);\n"
384             + "xhr.send('');\n"
385             + "</script></head><body></body></html>";
386 
387         ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
388                                         .addCredentials("jetty", "jetty".toCharArray());
389         getMockWebConnection().setResponse(URL_FIRST, html);
390         getMockWebConnection().setResponse(URL_SECOND, "Hello World");
391         loadPageWithAlerts(html, URL_FIRST, DEFAULT_WAIT_TIME);
392     }
393 
394     /**
395      * @throws Exception if an error occurs
396      */
397     @Test
398     @Alerts("HTTP ERROR 401")
399     public void basicAuthenticationXHRWithUsername() throws Exception {
400         final String html = DOCTYPE_HTML
401             + "<html><head><script>\n"
402             + "var xhr = new XMLHttpRequest();\n"
403             + "var handler = function() {\n"
404             + "  if (xhr.readyState == 4) {\n"
405             + "    var s = xhr.responseText.replace(/[\\r\\n]/g, '')"
406             + ".replace(/.*(HTTP ERROR \\d+).*/g, '$1');\n"
407             + "    alert(s);\n"
408             + "  }\n"
409             + "}\n"
410             + "xhr.onreadystatechange = handler;\n"
411             + "xhr.open('GET', '/foo', true, 'joe');\n"
412             + "xhr.send('');\n"
413             + "</script></head><body></body></html>";
414 
415         ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
416                                         .addCredentials("jetty", "jetty".toCharArray());
417         getMockWebConnection().setResponse(URL_FIRST, html);
418         getMockWebConnection().setResponse(URL_SECOND, "Hello World");
419         loadPageWithAlerts(html, URL_FIRST, DEFAULT_WAIT_TIME);
420     }
421 
422     /**
423      * @throws Exception if an error occurs
424      */
425     @Test
426     @Alerts("HTTP ERROR 401")
427     public void basicAuthenticationXHRWithUser() throws Exception {
428         final String html = DOCTYPE_HTML
429             + "<html><head><script>\n"
430             + "var xhr = new XMLHttpRequest();\n"
431             + "var handler = function() {\n"
432             + "  if (xhr.readyState == 4) {\n"
433             + "    var s = xhr.responseText.replace(/[\\r\\n]/g, '')"
434             + ".replace(/.*(HTTP ERROR \\d+).*/g, '$1');\n"
435             + "    alert(s);\n"
436             + "  }\n"
437             + "}\n"
438             + "xhr.onreadystatechange = handler;\n"
439             + "xhr.open('GET', '/foo', true, 'joe', 'secret');\n"
440             + "xhr.send('');\n"
441             + "</script></head><body></body></html>";
442 
443         ((DefaultCredentialsProvider) getWebClient().getCredentialsProvider())
444                                         .addCredentials("jetty", "jetty".toCharArray());
445         getMockWebConnection().setResponse(URL_FIRST, html);
446         getMockWebConnection().setResponse(URL_SECOND, "Hello World");
447         loadPageWithAlerts(html, URL_FIRST, DEFAULT_WAIT_TIME);
448     }
449 }