login_prompt_browsertest.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <algorithm>
6#include <list>
7#include <map>
8
9#include "base/utf_string_conversions.h"
10#include "chrome/browser/ui/browser.h"
11#include "chrome/browser/ui/login/login_prompt.h"
12#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
13#include "chrome/test/in_process_browser_test.h"
14#include "chrome/test/ui_test_utils.h"
15#include "content/browser/browser_thread.h"
16#include "content/browser/renderer_host/resource_dispatcher_host.h"
17#include "content/common/notification_service.h"
18#include "net/base/auth.h"
19
20namespace {
21
22class LoginPromptBrowserTest : public InProcessBrowserTest {
23 public:
24  LoginPromptBrowserTest()
25      : bad_password_(L"incorrect"), bad_username_(L"nouser") {
26    set_show_window(true);
27
28    auth_map_[L"foo"] = AuthInfo(L"testuser", L"foopassword");
29    auth_map_[L"bar"] = AuthInfo(L"testuser", L"barpassword");
30  }
31
32 protected:
33  void SetAuthFor(LoginHandler* handler);
34
35  struct AuthInfo {
36    std::wstring username_;
37    std::wstring password_;
38
39    AuthInfo() {}
40
41    AuthInfo(const std::wstring username,
42             const std::wstring password)
43        : username_(username), password_(password) {}
44  };
45
46  std::map<std::wstring, AuthInfo> auth_map_;
47  std::wstring bad_password_;
48  std::wstring bad_username_;
49};
50
51void LoginPromptBrowserTest::SetAuthFor(LoginHandler* handler) {
52  const net::AuthChallengeInfo* challenge = handler->auth_info();
53
54  ASSERT_TRUE(challenge);
55  std::map<std::wstring, AuthInfo>::iterator i =
56      auth_map_.find(challenge->realm);
57  EXPECT_TRUE(auth_map_.end() != i);
58  if (i != auth_map_.end()) {
59    const AuthInfo& info = i->second;
60    handler->SetAuth(WideToUTF16Hack(info.username_),
61                     WideToUTF16Hack(info.password_));
62  }
63}
64
65// Maintains a set of LoginHandlers that are currently active and
66// keeps a count of the notifications that were observed.
67class LoginPromptBrowserTestObserver : public NotificationObserver {
68 public:
69  LoginPromptBrowserTestObserver()
70      : auth_needed_count_(0),
71        auth_supplied_count_(0),
72        auth_cancelled_count_(0) {}
73
74  virtual void Observe(NotificationType type,
75                       const NotificationSource& source,
76                       const NotificationDetails& details);
77
78  void AddHandler(LoginHandler* handler);
79
80  void RemoveHandler(LoginHandler* handler);
81
82  void Register(const NotificationSource& source);
83
84  std::list<LoginHandler*> handlers_;
85
86  // The exact number of notifications we receive is depedent on the
87  // number of requests that were dispatched and is subject to a
88  // number of factors that we don't directly control here.  The
89  // values below should only be used qualitatively.
90  int auth_needed_count_;
91  int auth_supplied_count_;
92  int auth_cancelled_count_;
93
94 private:
95  NotificationRegistrar registrar_;
96
97  DISALLOW_COPY_AND_ASSIGN(LoginPromptBrowserTestObserver);
98};
99
100void LoginPromptBrowserTestObserver::Observe(
101    NotificationType type,
102    const NotificationSource& source,
103    const NotificationDetails& details) {
104  if (type == NotificationType::AUTH_NEEDED) {
105    LoginNotificationDetails* login_details =
106        Details<LoginNotificationDetails>(details).ptr();
107    AddHandler(login_details->handler());
108    auth_needed_count_++;
109  } else if (type == NotificationType::AUTH_SUPPLIED) {
110    AuthSuppliedLoginNotificationDetails* login_details =
111        Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
112    RemoveHandler(login_details->handler());
113    auth_supplied_count_++;
114  } else if (type == NotificationType::AUTH_CANCELLED) {
115    LoginNotificationDetails* login_details =
116        Details<LoginNotificationDetails>(details).ptr();
117    RemoveHandler(login_details->handler());
118    auth_cancelled_count_++;
119  }
120}
121
122void LoginPromptBrowserTestObserver::AddHandler(LoginHandler* handler) {
123  std::list<LoginHandler*>::iterator i = std::find(handlers_.begin(),
124                                                   handlers_.end(),
125                                                   handler);
126  EXPECT_TRUE(i == handlers_.end());
127  if (i == handlers_.end())
128    handlers_.push_back(handler);
129}
130
131void LoginPromptBrowserTestObserver::RemoveHandler(LoginHandler* handler) {
132  std::list<LoginHandler*>::iterator i = std::find(handlers_.begin(),
133                                                   handlers_.end(),
134                                                   handler);
135  EXPECT_TRUE(i != handlers_.end());
136  if (i != handlers_.end())
137    handlers_.erase(i);
138}
139
140void LoginPromptBrowserTestObserver::Register(
141    const NotificationSource& source) {
142  registrar_.Add(this, NotificationType::AUTH_NEEDED, source);
143  registrar_.Add(this, NotificationType::AUTH_SUPPLIED, source);
144  registrar_.Add(this, NotificationType::AUTH_CANCELLED, source);
145}
146
147template <NotificationType::Type T>
148class WindowedNavigationObserver
149    : public ui_test_utils::WindowedNotificationObserver {
150 public:
151  explicit WindowedNavigationObserver(NavigationController* controller)
152      : ui_test_utils::WindowedNotificationObserver(
153          T, Source<NavigationController>(controller)) {}
154};
155
156typedef WindowedNavigationObserver<NotificationType::LOAD_STOP>
157    WindowedLoadStopObserver;
158
159typedef WindowedNavigationObserver<NotificationType::AUTH_NEEDED>
160    WindowedAuthNeededObserver;
161
162typedef WindowedNavigationObserver<NotificationType::AUTH_CANCELLED>
163    WindowedAuthCancelledObserver;
164
165typedef WindowedNavigationObserver<NotificationType::AUTH_SUPPLIED>
166    WindowedAuthSuppliedObserver;
167
168const char* kPrefetchAuthPage = "files/login/prefetch.html";
169
170const char* kMultiRealmTestPage = "files/login/multi_realm.html";
171const int   kMultiRealmTestRealmCount = 2;
172const int   kMultiRealmTestResourceCount = 4;
173
174const char* kSingleRealmTestPage = "files/login/single_realm.html";
175const int   kSingleRealmTestResourceCount = 6;
176
177// Confirm that <link rel="prefetch"> targetting an auth required
178// resource does not provide a login dialog.  These types of requests
179// should instead just cancel the auth.
180
181// Unfortunately, this test doesn't assert on anything for its
182// correctness.  Instead, it relies on the auth dialog blocking the
183// browser, and triggering a timeout to cause failure when the
184// prefetch resource requires authorization.
185IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, PrefetchAuthCancels) {
186  ASSERT_TRUE(test_server()->Start());
187
188  GURL test_page = test_server()->GetURL(kPrefetchAuthPage);
189
190  class SetPrefetchForTest {
191   public:
192    explicit SetPrefetchForTest(bool prefetch)
193        : old_prefetch_state_(ResourceDispatcherHost::is_prefetch_enabled()) {
194      ResourceDispatcherHost::set_is_prefetch_enabled(prefetch);
195    }
196
197    ~SetPrefetchForTest() {
198      ResourceDispatcherHost::set_is_prefetch_enabled(old_prefetch_state_);
199    }
200   private:
201    bool old_prefetch_state_;
202  } set_prefetch_for_test(true);
203
204  TabContentsWrapper* contents =
205      browser()->GetSelectedTabContentsWrapper();
206  ASSERT_TRUE(contents);
207  NavigationController* controller = &contents->controller();
208  LoginPromptBrowserTestObserver observer;
209
210  observer.Register(Source<NavigationController>(controller));
211
212  WindowedLoadStopObserver load_stop_waiter(controller);
213  browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
214
215  load_stop_waiter.Wait();
216  EXPECT_TRUE(observer.handlers_.empty());
217  EXPECT_TRUE(test_server()->Stop());
218}
219
220// Test handling of resources that require authentication even though
221// the page they are included on doesn't.  In this case we should only
222// present the minimal number of prompts necessary for successfully
223// displaying the page.  First we check whether cancelling works as
224// expected.
225IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, MultipleRealmCancellation) {
226  ASSERT_TRUE(test_server()->Start());
227  GURL test_page = test_server()->GetURL(kMultiRealmTestPage);
228
229  TabContentsWrapper* contents =
230      browser()->GetSelectedTabContentsWrapper();
231  ASSERT_TRUE(contents);
232
233  NavigationController* controller = &contents->controller();
234  LoginPromptBrowserTestObserver observer;
235
236  observer.Register(Source<NavigationController>(controller));
237
238  WindowedLoadStopObserver load_stop_waiter(controller);
239
240  {
241    WindowedAuthNeededObserver auth_needed_waiter(controller);
242    browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
243    auth_needed_waiter.Wait();
244  }
245
246  int n_handlers = 0;
247
248  while (n_handlers < kMultiRealmTestRealmCount) {
249    WindowedAuthNeededObserver auth_needed_waiter(controller);
250
251    while (!observer.handlers_.empty()) {
252      WindowedAuthCancelledObserver auth_cancelled_waiter(controller);
253      LoginHandler* handler = *observer.handlers_.begin();
254
255      ASSERT_TRUE(handler);
256      n_handlers++;
257      handler->CancelAuth();
258      auth_cancelled_waiter.Wait();
259    }
260
261    if (n_handlers < kMultiRealmTestRealmCount)
262      auth_needed_waiter.Wait();
263  }
264
265  load_stop_waiter.Wait();
266
267  EXPECT_EQ(kMultiRealmTestRealmCount, n_handlers);
268  EXPECT_EQ(0, observer.auth_supplied_count_);
269  EXPECT_LT(0, observer.auth_needed_count_);
270  EXPECT_LT(0, observer.auth_cancelled_count_);
271  EXPECT_TRUE(test_server()->Stop());
272}
273
274// Similar to the MultipleRealmCancellation test above, but tests
275// whether supplying credentials work as exepcted.
276#if defined(OS_WIN)
277// See http://crbug.com/70960
278#define MAYBE_MultipleRealmConfirmation DISABLED_MultipleRealmConfirmation
279#else
280#define MAYBE_MultipleRealmConfirmation MultipleRealmConfirmation
281#endif
282IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest,
283                       MAYBE_MultipleRealmConfirmation) {
284  ASSERT_TRUE(test_server()->Start());
285  GURL test_page = test_server()->GetURL(kMultiRealmTestPage);
286
287  TabContentsWrapper* contents =
288      browser()->GetSelectedTabContentsWrapper();
289  ASSERT_TRUE(contents);
290
291  NavigationController* controller = &contents->controller();
292  LoginPromptBrowserTestObserver observer;
293
294  observer.Register(Source<NavigationController>(controller));
295
296  WindowedLoadStopObserver load_stop_waiter(controller);
297  int n_handlers = 0;
298
299  {
300    WindowedAuthNeededObserver auth_needed_waiter(controller);
301
302    browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
303    auth_needed_waiter.Wait();
304  }
305
306  while (n_handlers < kMultiRealmTestRealmCount) {
307    WindowedAuthNeededObserver auth_needed_waiter(controller);
308
309    while (!observer.handlers_.empty()) {
310      WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
311      LoginHandler* handler = *observer.handlers_.begin();
312
313      ASSERT_TRUE(handler);
314      n_handlers++;
315      SetAuthFor(handler);
316      auth_supplied_waiter.Wait();
317    }
318
319    if (n_handlers < kMultiRealmTestRealmCount)
320      auth_needed_waiter.Wait();
321  }
322
323  load_stop_waiter.Wait();
324
325  EXPECT_EQ(kMultiRealmTestRealmCount, n_handlers);
326  EXPECT_LT(0, observer.auth_needed_count_);
327  EXPECT_LT(0, observer.auth_supplied_count_);
328  EXPECT_EQ(0, observer.auth_cancelled_count_);
329  EXPECT_TRUE(test_server()->Stop());
330}
331
332// Testing for recovery from an incorrect password for the case where
333// there are multiple authenticated resources.
334// Marked as flaky.  See http://crbug.com/69266 and http://crbug.com/68860
335// TODO(asanka): Remove logging when timeout issues are resolved.
336IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, DISABLED_IncorrectConfirmation) {
337  ASSERT_TRUE(test_server()->Start());
338  GURL test_page = test_server()->GetURL(kSingleRealmTestPage);
339
340  TabContentsWrapper* contents =
341      browser()->GetSelectedTabContentsWrapper();
342  ASSERT_TRUE(contents);
343
344  NavigationController* controller = &contents->controller();
345  LoginPromptBrowserTestObserver observer;
346
347  observer.Register(Source<NavigationController>(controller));
348
349  WindowedLoadStopObserver load_stop_waiter(controller);
350
351  LOG(INFO) <<
352      "Begin test run "
353      "(tracing for potential hang. crbug.com/69266)";
354  {
355    WindowedAuthNeededObserver auth_needed_waiter(controller);
356    browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
357    LOG(INFO) << "Waiting for initial AUTH_NEEDED";
358    auth_needed_waiter.Wait();
359  }
360
361  EXPECT_FALSE(observer.handlers_.empty());
362
363  if (!observer.handlers_.empty()) {
364    WindowedAuthNeededObserver auth_needed_waiter(controller);
365    WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
366    LoginHandler* handler = *observer.handlers_.begin();
367
368    ASSERT_TRUE(handler);
369    handler->SetAuth(WideToUTF16Hack(bad_username_),
370                     WideToUTF16Hack(bad_password_));
371    LOG(INFO) << "Waiting for initial AUTH_SUPPLIED";
372    auth_supplied_waiter.Wait();
373
374    // The request should be retried after the incorrect password is
375    // supplied.  This should result in a new AUTH_NEEDED notification
376    // for the same realm.
377    LOG(INFO) << "Waiting for secondary AUTH_NEEDED";
378    auth_needed_waiter.Wait();
379  }
380
381  int n_handlers = 0;
382
383  while (n_handlers < 1) {
384    WindowedAuthNeededObserver auth_needed_waiter(controller);
385
386    while (!observer.handlers_.empty()) {
387      WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
388      LoginHandler* handler = *observer.handlers_.begin();
389
390      ASSERT_TRUE(handler);
391      n_handlers++;
392      SetAuthFor(handler);
393      LOG(INFO) << "Waiting for secondary AUTH_SUPPLIED";
394      auth_supplied_waiter.Wait();
395    }
396
397    if (n_handlers < 1) {
398      LOG(INFO) << "Waiting for additional AUTH_NEEDED";
399      auth_needed_waiter.Wait();
400    }
401  }
402
403  // The single realm test has only one realm, and thus only one login
404  // prompt.
405  EXPECT_EQ(1, n_handlers);
406  EXPECT_LT(0, observer.auth_needed_count_);
407  EXPECT_EQ(0, observer.auth_cancelled_count_);
408  EXPECT_EQ(observer.auth_needed_count_, observer.auth_supplied_count_);
409  LOG(INFO) << "Waiting for LOAD_STOP";
410  load_stop_waiter.Wait();
411  EXPECT_TRUE(test_server()->Stop());
412  LOG(INFO) << "Done with test";
413}
414
415// If the favicon is an authenticated resource, we shouldn't prompt
416// for credentials.  The same URL, if requested elsewhere should
417// prompt for credentials.
418IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, NoLoginPromptForFavicon) {
419  const char* kFaviconTestPage = "files/login/has_favicon.html";
420  const char* kFaviconResource = "auth-basic/favicon.gif";
421
422  ASSERT_TRUE(test_server()->Start());
423
424  TabContentsWrapper* contents =
425      browser()->GetSelectedTabContentsWrapper();
426  ASSERT_TRUE(contents);
427
428  NavigationController* controller = &contents->controller();
429  LoginPromptBrowserTestObserver observer;
430
431  observer.Register(Source<NavigationController>(controller));
432
433  // First load a page that has a favicon that requires
434  // authentication.  There should be no login prompt.
435  {
436    GURL test_page = test_server()->GetURL(kFaviconTestPage);
437    WindowedLoadStopObserver load_stop_waiter(controller);
438    browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
439    load_stop_waiter.Wait();
440  }
441
442  // Now request the same favicon, but directly as the document.
443  // There should be one login prompt.
444  {
445    GURL test_page = test_server()->GetURL(kFaviconResource);
446    WindowedLoadStopObserver load_stop_waiter(controller);
447    WindowedAuthNeededObserver auth_needed_waiter(controller);
448    browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
449    auth_needed_waiter.Wait();
450    ASSERT_EQ(1u, observer.handlers_.size());
451
452    while (!observer.handlers_.empty()) {
453      WindowedAuthCancelledObserver auth_cancelled_waiter(controller);
454      LoginHandler* handler = *observer.handlers_.begin();
455
456      ASSERT_TRUE(handler);
457      handler->CancelAuth();
458      auth_cancelled_waiter.Wait();
459    }
460
461    load_stop_waiter.Wait();
462  }
463
464  EXPECT_EQ(0, observer.auth_supplied_count_);
465  EXPECT_EQ(1, observer.auth_needed_count_);
466  EXPECT_EQ(1, observer.auth_cancelled_count_);
467  EXPECT_TRUE(test_server()->Stop());
468}
469
470}  // namespace
471