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