// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <list>
#include <map>
#include "base/utf_string_conversions.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/login/login_prompt.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/resource_dispatcher_host.h"
#include "content/common/notification_service.h"
#include "net/base/auth.h"
namespace {
class LoginPromptBrowserTest : public InProcessBrowserTest {
public:
LoginPromptBrowserTest()
: bad_password_(L"incorrect"), bad_username_(L"nouser") {
set_show_window(true);
auth_map_[L"foo"] = AuthInfo(L"testuser", L"foopassword");
auth_map_[L"bar"] = AuthInfo(L"testuser", L"barpassword");
}
protected:
void SetAuthFor(LoginHandler* handler);
struct AuthInfo {
std::wstring username_;
std::wstring password_;
AuthInfo() {}
AuthInfo(const std::wstring username,
const std::wstring password)
: username_(username), password_(password) {}
};
std::map<std::wstring, AuthInfo> auth_map_;
std::wstring bad_password_;
std::wstring bad_username_;
};
void LoginPromptBrowserTest::SetAuthFor(LoginHandler* handler) {
const net::AuthChallengeInfo* challenge = handler->auth_info();
ASSERT_TRUE(challenge);
std::map<std::wstring, AuthInfo>::iterator i =
auth_map_.find(challenge->realm);
EXPECT_TRUE(auth_map_.end() != i);
if (i != auth_map_.end()) {
const AuthInfo& info = i->second;
handler->SetAuth(WideToUTF16Hack(info.username_),
WideToUTF16Hack(info.password_));
}
}
// Maintains a set of LoginHandlers that are currently active and
// keeps a count of the notifications that were observed.
class LoginPromptBrowserTestObserver : public NotificationObserver {
public:
LoginPromptBrowserTestObserver()
: auth_needed_count_(0),
auth_supplied_count_(0),
auth_cancelled_count_(0) {}
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
void AddHandler(LoginHandler* handler);
void RemoveHandler(LoginHandler* handler);
void Register(const NotificationSource& source);
std::list<LoginHandler*> handlers_;
// The exact number of notifications we receive is depedent on the
// number of requests that were dispatched and is subject to a
// number of factors that we don't directly control here. The
// values below should only be used qualitatively.
int auth_needed_count_;
int auth_supplied_count_;
int auth_cancelled_count_;
private:
NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(LoginPromptBrowserTestObserver);
};
void LoginPromptBrowserTestObserver::Observe(
NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (type == NotificationType::AUTH_NEEDED) {
LoginNotificationDetails* login_details =
Details<LoginNotificationDetails>(details).ptr();
AddHandler(login_details->handler());
auth_needed_count_++;
} else if (type == NotificationType::AUTH_SUPPLIED) {
AuthSuppliedLoginNotificationDetails* login_details =
Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
RemoveHandler(login_details->handler());
auth_supplied_count_++;
} else if (type == NotificationType::AUTH_CANCELLED) {
LoginNotificationDetails* login_details =
Details<LoginNotificationDetails>(details).ptr();
RemoveHandler(login_details->handler());
auth_cancelled_count_++;
}
}
void LoginPromptBrowserTestObserver::AddHandler(LoginHandler* handler) {
std::list<LoginHandler*>::iterator i = std::find(handlers_.begin(),
handlers_.end(),
handler);
EXPECT_TRUE(i == handlers_.end());
if (i == handlers_.end())
handlers_.push_back(handler);
}
void LoginPromptBrowserTestObserver::RemoveHandler(LoginHandler* handler) {
std::list<LoginHandler*>::iterator i = std::find(handlers_.begin(),
handlers_.end(),
handler);
EXPECT_TRUE(i != handlers_.end());
if (i != handlers_.end())
handlers_.erase(i);
}
void LoginPromptBrowserTestObserver::Register(
const NotificationSource& source) {
registrar_.Add(this, NotificationType::AUTH_NEEDED, source);
registrar_.Add(this, NotificationType::AUTH_SUPPLIED, source);
registrar_.Add(this, NotificationType::AUTH_CANCELLED, source);
}
template <NotificationType::Type T>
class WindowedNavigationObserver
: public ui_test_utils::WindowedNotificationObserver {
public:
explicit WindowedNavigationObserver(NavigationController* controller)
: ui_test_utils::WindowedNotificationObserver(
T, Source<NavigationController>(controller)) {}
};
typedef WindowedNavigationObserver<NotificationType::LOAD_STOP>
WindowedLoadStopObserver;
typedef WindowedNavigationObserver<NotificationType::AUTH_NEEDED>
WindowedAuthNeededObserver;
typedef WindowedNavigationObserver<NotificationType::AUTH_CANCELLED>
WindowedAuthCancelledObserver;
typedef WindowedNavigationObserver<NotificationType::AUTH_SUPPLIED>
WindowedAuthSuppliedObserver;
const char* kPrefetchAuthPage = "files/login/prefetch.html";
const char* kMultiRealmTestPage = "files/login/multi_realm.html";
const int kMultiRealmTestRealmCount = 2;
const int kMultiRealmTestResourceCount = 4;
const char* kSingleRealmTestPage = "files/login/single_realm.html";
const int kSingleRealmTestResourceCount = 6;
// Confirm that <link rel="prefetch"> targetting an auth required
// resource does not provide a login dialog. These types of requests
// should instead just cancel the auth.
// Unfortunately, this test doesn't assert on anything for its
// correctness. Instead, it relies on the auth dialog blocking the
// browser, and triggering a timeout to cause failure when the
// prefetch resource requires authorization.
IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, PrefetchAuthCancels) {
ASSERT_TRUE(test_server()->Start());
GURL test_page = test_server()->GetURL(kPrefetchAuthPage);
class SetPrefetchForTest {
public:
explicit SetPrefetchForTest(bool prefetch)
: old_prefetch_state_(ResourceDispatcherHost::is_prefetch_enabled()) {
ResourceDispatcherHost::set_is_prefetch_enabled(prefetch);
}
~SetPrefetchForTest() {
ResourceDispatcherHost::set_is_prefetch_enabled(old_prefetch_state_);
}
private:
bool old_prefetch_state_;
} set_prefetch_for_test(true);
TabContentsWrapper* contents =
browser()->GetSelectedTabContentsWrapper();
ASSERT_TRUE(contents);
NavigationController* controller = &contents->controller();
LoginPromptBrowserTestObserver observer;
observer.Register(Source<NavigationController>(controller));
WindowedLoadStopObserver load_stop_waiter(controller);
browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
load_stop_waiter.Wait();
EXPECT_TRUE(observer.handlers_.empty());
EXPECT_TRUE(test_server()->Stop());
}
// Test handling of resources that require authentication even though
// the page they are included on doesn't. In this case we should only
// present the minimal number of prompts necessary for successfully
// displaying the page. First we check whether cancelling works as
// expected.
IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, MultipleRealmCancellation) {
ASSERT_TRUE(test_server()->Start());
GURL test_page = test_server()->GetURL(kMultiRealmTestPage);
TabContentsWrapper* contents =
browser()->GetSelectedTabContentsWrapper();
ASSERT_TRUE(contents);
NavigationController* controller = &contents->controller();
LoginPromptBrowserTestObserver observer;
observer.Register(Source<NavigationController>(controller));
WindowedLoadStopObserver load_stop_waiter(controller);
{
WindowedAuthNeededObserver auth_needed_waiter(controller);
browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
auth_needed_waiter.Wait();
}
int n_handlers = 0;
while (n_handlers < kMultiRealmTestRealmCount) {
WindowedAuthNeededObserver auth_needed_waiter(controller);
while (!observer.handlers_.empty()) {
WindowedAuthCancelledObserver auth_cancelled_waiter(controller);
LoginHandler* handler = *observer.handlers_.begin();
ASSERT_TRUE(handler);
n_handlers++;
handler->CancelAuth();
auth_cancelled_waiter.Wait();
}
if (n_handlers < kMultiRealmTestRealmCount)
auth_needed_waiter.Wait();
}
load_stop_waiter.Wait();
EXPECT_EQ(kMultiRealmTestRealmCount, n_handlers);
EXPECT_EQ(0, observer.auth_supplied_count_);
EXPECT_LT(0, observer.auth_needed_count_);
EXPECT_LT(0, observer.auth_cancelled_count_);
EXPECT_TRUE(test_server()->Stop());
}
// Similar to the MultipleRealmCancellation test above, but tests
// whether supplying credentials work as exepcted.
#if defined(OS_WIN)
// See http://crbug.com/70960
#define MAYBE_MultipleRealmConfirmation DISABLED_MultipleRealmConfirmation
#else
#define MAYBE_MultipleRealmConfirmation MultipleRealmConfirmation
#endif
IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest,
MAYBE_MultipleRealmConfirmation) {
ASSERT_TRUE(test_server()->Start());
GURL test_page = test_server()->GetURL(kMultiRealmTestPage);
TabContentsWrapper* contents =
browser()->GetSelectedTabContentsWrapper();
ASSERT_TRUE(contents);
NavigationController* controller = &contents->controller();
LoginPromptBrowserTestObserver observer;
observer.Register(Source<NavigationController>(controller));
WindowedLoadStopObserver load_stop_waiter(controller);
int n_handlers = 0;
{
WindowedAuthNeededObserver auth_needed_waiter(controller);
browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
auth_needed_waiter.Wait();
}
while (n_handlers < kMultiRealmTestRealmCount) {
WindowedAuthNeededObserver auth_needed_waiter(controller);
while (!observer.handlers_.empty()) {
WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
LoginHandler* handler = *observer.handlers_.begin();
ASSERT_TRUE(handler);
n_handlers++;
SetAuthFor(handler);
auth_supplied_waiter.Wait();
}
if (n_handlers < kMultiRealmTestRealmCount)
auth_needed_waiter.Wait();
}
load_stop_waiter.Wait();
EXPECT_EQ(kMultiRealmTestRealmCount, n_handlers);
EXPECT_LT(0, observer.auth_needed_count_);
EXPECT_LT(0, observer.auth_supplied_count_);
EXPECT_EQ(0, observer.auth_cancelled_count_);
EXPECT_TRUE(test_server()->Stop());
}
// Testing for recovery from an incorrect password for the case where
// there are multiple authenticated resources.
// Marked as flaky. See http://crbug.com/69266 and http://crbug.com/68860
// TODO(asanka): Remove logging when timeout issues are resolved.
IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, DISABLED_IncorrectConfirmation) {
ASSERT_TRUE(test_server()->Start());
GURL test_page = test_server()->GetURL(kSingleRealmTestPage);
TabContentsWrapper* contents =
browser()->GetSelectedTabContentsWrapper();
ASSERT_TRUE(contents);
NavigationController* controller = &contents->controller();
LoginPromptBrowserTestObserver observer;
observer.Register(Source<NavigationController>(controller));
WindowedLoadStopObserver load_stop_waiter(controller);
LOG(INFO) <<
"Begin test run "
"(tracing for potential hang. crbug.com/69266)";
{
WindowedAuthNeededObserver auth_needed_waiter(controller);
browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
LOG(INFO) << "Waiting for initial AUTH_NEEDED";
auth_needed_waiter.Wait();
}
EXPECT_FALSE(observer.handlers_.empty());
if (!observer.handlers_.empty()) {
WindowedAuthNeededObserver auth_needed_waiter(controller);
WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
LoginHandler* handler = *observer.handlers_.begin();
ASSERT_TRUE(handler);
handler->SetAuth(WideToUTF16Hack(bad_username_),
WideToUTF16Hack(bad_password_));
LOG(INFO) << "Waiting for initial AUTH_SUPPLIED";
auth_supplied_waiter.Wait();
// The request should be retried after the incorrect password is
// supplied. This should result in a new AUTH_NEEDED notification
// for the same realm.
LOG(INFO) << "Waiting for secondary AUTH_NEEDED";
auth_needed_waiter.Wait();
}
int n_handlers = 0;
while (n_handlers < 1) {
WindowedAuthNeededObserver auth_needed_waiter(controller);
while (!observer.handlers_.empty()) {
WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
LoginHandler* handler = *observer.handlers_.begin();
ASSERT_TRUE(handler);
n_handlers++;
SetAuthFor(handler);
LOG(INFO) << "Waiting for secondary AUTH_SUPPLIED";
auth_supplied_waiter.Wait();
}
if (n_handlers < 1) {
LOG(INFO) << "Waiting for additional AUTH_NEEDED";
auth_needed_waiter.Wait();
}
}
// The single realm test has only one realm, and thus only one login
// prompt.
EXPECT_EQ(1, n_handlers);
EXPECT_LT(0, observer.auth_needed_count_);
EXPECT_EQ(0, observer.auth_cancelled_count_);
EXPECT_EQ(observer.auth_needed_count_, observer.auth_supplied_count_);
LOG(INFO) << "Waiting for LOAD_STOP";
load_stop_waiter.Wait();
EXPECT_TRUE(test_server()->Stop());
LOG(INFO) << "Done with test";
}
// If the favicon is an authenticated resource, we shouldn't prompt
// for credentials. The same URL, if requested elsewhere should
// prompt for credentials.
IN_PROC_BROWSER_TEST_F(LoginPromptBrowserTest, NoLoginPromptForFavicon) {
const char* kFaviconTestPage = "files/login/has_favicon.html";
const char* kFaviconResource = "auth-basic/favicon.gif";
ASSERT_TRUE(test_server()->Start());
TabContentsWrapper* contents =
browser()->GetSelectedTabContentsWrapper();
ASSERT_TRUE(contents);
NavigationController* controller = &contents->controller();
LoginPromptBrowserTestObserver observer;
observer.Register(Source<NavigationController>(controller));
// First load a page that has a favicon that requires
// authentication. There should be no login prompt.
{
GURL test_page = test_server()->GetURL(kFaviconTestPage);
WindowedLoadStopObserver load_stop_waiter(controller);
browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
load_stop_waiter.Wait();
}
// Now request the same favicon, but directly as the document.
// There should be one login prompt.
{
GURL test_page = test_server()->GetURL(kFaviconResource);
WindowedLoadStopObserver load_stop_waiter(controller);
WindowedAuthNeededObserver auth_needed_waiter(controller);
browser()->OpenURL(test_page, GURL(), CURRENT_TAB, PageTransition::TYPED);
auth_needed_waiter.Wait();
ASSERT_EQ(1u, observer.handlers_.size());
while (!observer.handlers_.empty()) {
WindowedAuthCancelledObserver auth_cancelled_waiter(controller);
LoginHandler* handler = *observer.handlers_.begin();
ASSERT_TRUE(handler);
handler->CancelAuth();
auth_cancelled_waiter.Wait();
}
load_stop_waiter.Wait();
}
EXPECT_EQ(0, observer.auth_supplied_count_);
EXPECT_EQ(1, observer.auth_needed_count_);
EXPECT_EQ(1, observer.auth_cancelled_count_);
EXPECT_TRUE(test_server()->Stop());
}
} // namespace