Home | History | Annotate | Download | only in login
      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 
     20 namespace {
     21 
     22 class 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 
     51 void 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.
     67 class 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 
    100 void 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 
    122 void 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 
    131 void 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 
    140 void 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 
    147 template <NotificationType::Type T>
    148 class 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 
    156 typedef WindowedNavigationObserver<NotificationType::LOAD_STOP>
    157     WindowedLoadStopObserver;
    158 
    159 typedef WindowedNavigationObserver<NotificationType::AUTH_NEEDED>
    160     WindowedAuthNeededObserver;
    161 
    162 typedef WindowedNavigationObserver<NotificationType::AUTH_CANCELLED>
    163     WindowedAuthCancelledObserver;
    164 
    165 typedef WindowedNavigationObserver<NotificationType::AUTH_SUPPLIED>
    166     WindowedAuthSuppliedObserver;
    167 
    168 const char* kPrefetchAuthPage = "files/login/prefetch.html";
    169 
    170 const char* kMultiRealmTestPage = "files/login/multi_realm.html";
    171 const int   kMultiRealmTestRealmCount = 2;
    172 const int   kMultiRealmTestResourceCount = 4;
    173 
    174 const char* kSingleRealmTestPage = "files/login/single_realm.html";
    175 const 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.
    185 IN_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.
    225 IN_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
    282 IN_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.
    336 IN_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.
    418 IN_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