diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-02 21:18:33 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-02 21:18:33 +0000 |
commit | 8ecad5ed4a616e8c5023561c224e81e09a37d79a (patch) | |
tree | 86f596a20c73edf5a39eb0a4e589fe654c15ba2c /chrome/browser/ui/login | |
parent | 96ccc7ec1f107b74a697ced6d08c4150efb5c297 (diff) | |
download | chromium_src-8ecad5ed4a616e8c5023561c224e81e09a37d79a.zip chromium_src-8ecad5ed4a616e8c5023561c224e81e09a37d79a.tar.gz chromium_src-8ecad5ed4a616e8c5023561c224e81e09a37d79a.tar.bz2 |
Move:
file_path_watcher into subdir
profile* into profiles/ subdir
login* into ui/login
visitedlink* into subdir
BUG=none
TEST=none
TBR=brettw
Review URL: http://codereview.chromium.org/5606002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@68069 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ui/login')
-rw-r--r-- | chrome/browser/ui/login/login_model.h | 35 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt.cc | 453 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt.h | 214 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt_gtk.cc | 199 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt_mac.h | 34 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt_mac.mm | 191 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt_uitest.cc | 280 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt_unittest.cc | 42 | ||||
-rw-r--r-- | chrome/browser/ui/login/login_prompt_win.cc | 147 |
9 files changed, 1595 insertions, 0 deletions
diff --git a/chrome/browser/ui/login/login_model.h b/chrome/browser/ui/login/login_model.h new file mode 100644 index 0000000..94d3478 --- /dev/null +++ b/chrome/browser/ui/login/login_model.h @@ -0,0 +1,35 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_UI_LOGIN_LOGIN_MODEL_H_ +#define CHROME_BROWSER_UI_LOGIN_LOGIN_MODEL_H_ +#pragma once + +#include <string> + +// Simple Model & Observer interfaces for a LoginView to facilitate exchanging +// information. +class LoginModelObserver { + public: + // Called by the model when a username,password pair has been identified + // as a match for the pending login prompt. + virtual void OnAutofillDataAvailable(const std::wstring& username, + const std::wstring& password) = 0; + + protected: + virtual ~LoginModelObserver() {} +}; + +class LoginModel { + public: + // Set the observer interested in the data from the model. + // observer can be null, signifying there is no longer any observer + // interested in the data. + virtual void SetObserver(LoginModelObserver* observer) = 0; + + protected: + virtual ~LoginModel() {} +}; + +#endif // CHROME_BROWSER_UI_LOGIN_LOGIN_MODEL_H_ diff --git a/chrome/browser/ui/login/login_prompt.cc b/chrome/browser/ui/login/login_prompt.cc new file mode 100644 index 0000000..d653457 --- /dev/null +++ b/chrome/browser/ui/login/login_prompt.cc @@ -0,0 +1,453 @@ +// Copyright (c) 2009 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 "chrome/browser/ui/login/login_prompt.h" + +#include <vector> + +#include "app/l10n_util.h" +#include "base/command_line.h" +#include "base/lock.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "chrome/browser/tab_contents/constrained_window.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" +#include "grit/generated_resources.h" +#include "net/base/auth.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request.h" + +using webkit_glue::PasswordForm; + +class LoginHandlerImpl; + +// Helper to remove the ref from an net::URLRequest to the LoginHandler. +// Should only be called from the IO thread, since it accesses an +// net::URLRequest. +void ResetLoginHandlerForRequest(net::URLRequest* request) { + ResourceDispatcherHostRequestInfo* info = + ResourceDispatcherHost::InfoForRequest(request); + if (!info) + return; + + info->set_login_handler(NULL); +} + +// Get the signon_realm under which this auth info should be stored. +// +// The format of the signon_realm for proxy auth is: +// proxy-host/auth-realm +// The format of the signon_realm for server auth is: +// url-scheme://url-host[:url-port]/auth-realm +// +// Be careful when changing this function, since you could make existing +// saved logins un-retrievable. +std::string GetSignonRealm(const GURL& url, + const net::AuthChallengeInfo& auth_info) { + std::string signon_realm; + if (auth_info.is_proxy) { + signon_realm = WideToASCII(auth_info.host_and_port); + signon_realm.append("/"); + } else { + // Take scheme, host, and port from the url. + signon_realm = url.GetOrigin().spec(); + // This ends with a "/". + } + signon_realm.append(WideToUTF8(auth_info.realm)); + return signon_realm; +} + +// ---------------------------------------------------------------------------- +// LoginHandler + +LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, + net::URLRequest* request) + : handled_auth_(false), + dialog_(NULL), + auth_info_(auth_info), + request_(request), + password_manager_(NULL), + login_model_(NULL) { + // This constructor is called on the I/O thread, so we cannot load the nib + // here. BuildViewForPasswordManager() will be invoked on the UI thread + // later, so wait with loading the nib until then. + DCHECK(request_) << "LoginHandler constructed with NULL request"; + DCHECK(auth_info_) << "LoginHandler constructed with NULL auth info"; + + AddRef(); // matched by LoginHandler::ReleaseSoon(). + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::AddObservers)); + + if (!ResourceDispatcherHost::RenderViewForRequest( + request_, &render_process_host_id_, &tab_contents_id_)) { + NOTREACHED(); + } +} + +LoginHandler::~LoginHandler() { + SetModel(NULL); +} + +void LoginHandler::SetPasswordForm(const webkit_glue::PasswordForm& form) { + password_form_ = form; +} + +void LoginHandler::SetPasswordManager(PasswordManager* password_manager) { + password_manager_ = password_manager; +} + +TabContents* LoginHandler::GetTabContentsForLogin() const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + return tab_util::GetTabContentsByID(render_process_host_id_, + tab_contents_id_); +} + +void LoginHandler::SetAuth(const std::wstring& username, + const std::wstring& password) { + if (WasAuthHandled(true)) + return; + + // Tell the password manager the credentials were submitted / accepted. + if (password_manager_) { + password_form_.username_value = WideToUTF16Hack(username); + password_form_.password_value = WideToUTF16Hack(password); + password_manager_->ProvisionallySavePassword(password_form_); + } + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, &LoginHandler::NotifyAuthSupplied, username, password)); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod( + this, &LoginHandler::SetAuthDeferred, username, password)); +} + +void LoginHandler::CancelAuth() { + if (WasAuthHandled(true)) + return; + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled)); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred)); +} + +void LoginHandler::OnRequestCancelled() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) << + "Why is OnRequestCancelled called from the UI thread?"; + + // Reference is no longer valid. + request_ = NULL; + + // Give up on auth if the request was cancelled. + CancelAuth(); +} + +void LoginHandler::AddObservers() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + registrar_.Add(this, NotificationType::AUTH_SUPPLIED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::AUTH_CANCELLED, + NotificationService::AllSources()); +} + +void LoginHandler::RemoveObservers() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + registrar_.Remove(this, NotificationType::AUTH_SUPPLIED, + NotificationService::AllSources()); + registrar_.Remove(this, NotificationType::AUTH_CANCELLED, + NotificationService::AllSources()); + + DCHECK(registrar_.IsEmpty()); +} + +void LoginHandler::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(type == NotificationType::AUTH_SUPPLIED || + type == NotificationType::AUTH_CANCELLED); + + TabContents* requesting_contents = GetTabContentsForLogin(); + if (!requesting_contents) + return; + + NavigationController* this_controller = &requesting_contents->controller(); + NavigationController* that_controller = + Source<NavigationController>(source).ptr(); + + // Only handle notifications from other handlers. + if (this_controller == that_controller) + return; + + LoginNotificationDetails* login_details = + Details<LoginNotificationDetails>(details).ptr(); + + // Only handle notification for the identical auth info. + if (*login_details->handler()->auth_info() != *auth_info()) + return; + + // Set or cancel the auth in this handler. + if (type == NotificationType::AUTH_SUPPLIED) { + AuthSuppliedLoginNotificationDetails* supplied_details = + Details<AuthSuppliedLoginNotificationDetails>(details).ptr(); + SetAuth(supplied_details->username(), supplied_details->password()); + } else { + DCHECK(type == NotificationType::AUTH_CANCELLED); + CancelAuth(); + } +} + +void LoginHandler::SetModel(LoginModel* model) { + if (login_model_) + login_model_->SetObserver(NULL); + login_model_ = model; + if (login_model_) + login_model_->SetObserver(this); +} + +void LoginHandler::SetDialog(ConstrainedWindow* dialog) { + dialog_ = dialog; +} + +void LoginHandler::NotifyAuthNeeded() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (WasAuthHandled(false)) + return; + + TabContents* requesting_contents = GetTabContentsForLogin(); + if (!requesting_contents) + return; + + NotificationService* service = NotificationService::current(); + NavigationController* controller = &requesting_contents->controller(); + LoginNotificationDetails details(this); + + service->Notify(NotificationType::AUTH_NEEDED, + Source<NavigationController>(controller), + Details<LoginNotificationDetails>(&details)); +} + +void LoginHandler::NotifyAuthCancelled() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(WasAuthHandled(false)); + + TabContents* requesting_contents = GetTabContentsForLogin(); + if (!requesting_contents) + return; + + NotificationService* service = NotificationService::current(); + NavigationController* controller = &requesting_contents->controller(); + LoginNotificationDetails details(this); + + service->Notify(NotificationType::AUTH_CANCELLED, + Source<NavigationController>(controller), + Details<LoginNotificationDetails>(&details)); +} + +void LoginHandler::NotifyAuthSupplied(const std::wstring& username, + const std::wstring& password) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(WasAuthHandled(false)); + + TabContents* requesting_contents = GetTabContentsForLogin(); + if (!requesting_contents) + return; + + NotificationService* service = NotificationService::current(); + NavigationController* controller = &requesting_contents->controller(); + AuthSuppliedLoginNotificationDetails details(this, username, password); + + service->Notify(NotificationType::AUTH_SUPPLIED, + Source<NavigationController>(controller), + Details<AuthSuppliedLoginNotificationDetails>(&details)); +} + +void LoginHandler::ReleaseSoon() { + if (!WasAuthHandled(true)) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled)); + } + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, &LoginHandler::RemoveObservers)); + + // Delete this object once all InvokeLaters have been called. + BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this); +} + +// Returns whether authentication had been handled (SetAuth or CancelAuth). +// If |set_handled| is true, it will mark authentication as handled. +bool LoginHandler::WasAuthHandled(bool set_handled) { + AutoLock lock(handled_auth_lock_); + bool was_handled = handled_auth_; + if (set_handled) + handled_auth_ = true; + return was_handled; +} + +// Calls SetAuth from the IO loop. +void LoginHandler::SetAuthDeferred(const std::wstring& username, + const std::wstring& password) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (request_) { + request_->SetAuth(WideToUTF16Hack(username), WideToUTF16Hack(password)); + ResetLoginHandlerForRequest(request_); + } +} + +// Calls CancelAuth from the IO loop. +void LoginHandler::CancelAuthDeferred() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (request_) { + request_->CancelAuth(); + // Verify that CancelAuth doesn't destroy the request via our delegate. + DCHECK(request_ != NULL); + ResetLoginHandlerForRequest(request_); + } +} + +// Closes the view_contents from the UI loop. +void LoginHandler::CloseContentsDeferred() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // The hosting ConstrainedWindow may have been freed. + if (dialog_) + dialog_->CloseConstrainedWindow(); +} + +// ---------------------------------------------------------------------------- +// LoginDialogTask + +// This task is run on the UI thread and creates a constrained window with +// a LoginView to prompt the user. The response will be sent to LoginHandler, +// which then routes it to the net::URLRequest on the I/O thread. +class LoginDialogTask : public Task { + public: + LoginDialogTask(const GURL& request_url, + net::AuthChallengeInfo* auth_info, + LoginHandler* handler) + : request_url_(request_url), auth_info_(auth_info), handler_(handler) { + } + virtual ~LoginDialogTask() { + } + + void Run() { + TabContents* parent_contents = handler_->GetTabContentsForLogin(); + if (!parent_contents) { + // The request may have been cancelled, or it may be for a renderer + // not hosted by a tab (e.g. an extension). Cancel just in case + // (cancelling twice is a no-op). + handler_->CancelAuth(); + return; + } + + // Tell the password manager to look for saved passwords. + TabContentsWrapper** wrapper = + TabContentsWrapper::property_accessor()->GetProperty( + parent_contents->property_bag()); + if (!wrapper) + return; + PasswordManager* password_manager = (*wrapper)->GetPasswordManager(); + std::vector<PasswordForm> v; + MakeInputForPasswordManager(&v); + password_manager->PasswordFormsFound(v); + handler_->SetPasswordManager(password_manager); + + std::wstring explanation = auth_info_->realm.empty() ? + l10n_util::GetStringF(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM, + auth_info_->host_and_port) : + l10n_util::GetStringF(IDS_LOGIN_DIALOG_DESCRIPTION, + auth_info_->host_and_port, + auth_info_->realm); + handler_->BuildViewForPasswordManager(password_manager, + explanation); + } + + private: + // Helper to create a PasswordForm and stuff it into a vector as input + // for PasswordManager::PasswordFormsFound, the hook into PasswordManager. + void MakeInputForPasswordManager( + std::vector<PasswordForm>* password_manager_input) { + PasswordForm dialog_form; + if (LowerCaseEqualsASCII(auth_info_->scheme, "basic")) { + dialog_form.scheme = PasswordForm::SCHEME_BASIC; + } else if (LowerCaseEqualsASCII(auth_info_->scheme, "digest")) { + dialog_form.scheme = PasswordForm::SCHEME_DIGEST; + } else { + dialog_form.scheme = PasswordForm::SCHEME_OTHER; + } + std::string host_and_port(WideToASCII(auth_info_->host_and_port)); + if (auth_info_->is_proxy) { + std::string origin = host_and_port; + // We don't expect this to already start with http:// or https://. + DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0); + origin = std::string("http://") + origin; + dialog_form.origin = GURL(origin); + } else if (net::GetHostAndPort(request_url_) != host_and_port) { + dialog_form.origin = GURL(); + NOTREACHED(); // crbug.com/32718 + } else { + dialog_form.origin = GURL(request_url_.scheme() + "://" + host_and_port); + } + dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info_); + password_manager_input->push_back(dialog_form); + // Set the password form for the handler (by copy). + handler_->SetPasswordForm(dialog_form); + } + + // The url from the net::URLRequest initiating the auth challenge. + GURL request_url_; + + // Info about who/where/what is asking for authentication. + scoped_refptr<net::AuthChallengeInfo> auth_info_; + + // Where to send the authentication when obtained. + // This is owned by the ResourceDispatcherHost that invoked us. + LoginHandler* handler_; + + DISALLOW_COPY_AND_ASSIGN(LoginDialogTask); +}; + +// ---------------------------------------------------------------------------- +// Public API + +LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info, + net::URLRequest* request) { + LoginHandler* handler = LoginHandler::Create(auth_info, request); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, new LoginDialogTask( + request->url(), auth_info, handler)); + return handler; +} diff --git a/chrome/browser/ui/login/login_prompt.h b/chrome/browser/ui/login/login_prompt.h new file mode 100644 index 0000000..8e3d15e --- /dev/null +++ b/chrome/browser/ui/login/login_prompt.h @@ -0,0 +1,214 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_UI_LOGIN_LOGIN_PROMPT_H_ +#define CHROME_BROWSER_UI_LOGIN_LOGIN_PROMPT_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/ref_counted.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +namespace net { +class AuthChallengeInfo; +class URLRequest; +} // namespace net + +class ConstrainedWindow; +class GURL; + +// This is the base implementation for the OS-specific classes that route +// authentication info to the net::URLRequest that needs it. These functions +// must be implemented in a thread safe manner. +class LoginHandler : public base::RefCountedThreadSafe<LoginHandler>, + public LoginModelObserver, + public NotificationObserver { + public: + LoginHandler(net::AuthChallengeInfo* auth_info, net::URLRequest* request); + virtual ~LoginHandler(); + + // Builds the platform specific LoginHandler. Used from within + // CreateLoginPrompt() which creates tasks. + static LoginHandler* Create(net::AuthChallengeInfo* auth_info, + net::URLRequest* request); + + // Initializes the underlying platform specific view. + virtual void BuildViewForPasswordManager(PasswordManager* manager, + std::wstring explanation) = 0; + + // Sets information about the authentication type (|form|) and the + // |password_manager| for this profile. + void SetPasswordForm(const webkit_glue::PasswordForm& form); + void SetPasswordManager(PasswordManager* password_manager); + + // Returns the TabContents that needs authentication. + TabContents* GetTabContentsForLogin() const; + + // Resend the request with authentication credentials. + // This function can be called from either thread. + void SetAuth(const std::wstring& username, const std::wstring& password); + + // Display the error page without asking for credentials again. + // This function can be called from either thread. + void CancelAuth(); + + // Notify the handler that the request was cancelled. + // This function can only be called from the IO thread. + void OnRequestCancelled(); + + // Implements the NotificationObserver interface. + // Listens for AUTH_SUPPLIED and AUTH_CANCELLED notifications from other + // LoginHandlers so that this LoginHandler has the chance to dismiss itself + // if it was waiting for the same authentication. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + protected: + void SetModel(LoginModel* model); + + void SetDialog(ConstrainedWindow* dialog); + + // Notify observers that authentication is needed. + void NotifyAuthNeeded(); + + // Performs necessary cleanup before deletion. + void ReleaseSoon(); + + // Who/where/what asked for the authentication. + net::AuthChallengeInfo* auth_info() const { return auth_info_.get(); } + + private: + // Starts observing notifications from other LoginHandlers. + void AddObservers(); + + // Stops observing notifications from other LoginHandlers. + void RemoveObservers(); + + // Notify observers that authentication is supplied. + void NotifyAuthSupplied(const std::wstring& username, + const std::wstring& password); + + // Notify observers that authentication is cancelled. + void NotifyAuthCancelled(); + + // Returns whether authentication had been handled (SetAuth or CancelAuth). + // If |set_handled| is true, it will mark authentication as handled. + bool WasAuthHandled(bool set_handled); + + // Calls SetAuth from the IO loop. + void SetAuthDeferred(const std::wstring& username, + const std::wstring& password); + + // Calls CancelAuth from the IO loop. + void CancelAuthDeferred(); + + // Closes the view_contents from the UI loop. + void CloseContentsDeferred(); + + // True if we've handled auth (SetAuth or CancelAuth has been called). + bool handled_auth_; + Lock handled_auth_lock_; + + // The ConstrainedWindow that is hosting our LoginView. + // This should only be accessed on the UI loop. + ConstrainedWindow* dialog_; + + // Who/where/what asked for the authentication. + scoped_refptr<net::AuthChallengeInfo> auth_info_; + + // The request that wants login data. + // This should only be accessed on the IO loop. + net::URLRequest* request_; + + // The PasswordForm sent to the PasswordManager. This is so we can refer to it + // when later notifying the password manager if the credentials were accepted + // or rejected. + // This should only be accessed on the UI loop. + webkit_glue::PasswordForm password_form_; + + // Points to the password manager owned by the TabContents requesting auth. + // Can be null if the TabContents is not a TabContents. + // This should only be accessed on the UI loop. + PasswordManager* password_manager_; + + // Cached from the net::URLRequest, in case it goes NULL on us. + int render_process_host_id_; + int tab_contents_id_; + + // If not null, points to a model we need to notify of our own destruction + // so it doesn't try and access this when its too late. + LoginModel* login_model_; + + // Observes other login handlers so this login handler can respond. + NotificationRegistrar registrar_; +}; + +// Details to provide the NotificationObserver. Used by the automation proxy +// for testing. +class LoginNotificationDetails { + public: + explicit LoginNotificationDetails(LoginHandler* handler) + : handler_(handler) {} + LoginHandler* handler() const { return handler_; } + + private: + LoginNotificationDetails() {} + + LoginHandler* handler_; // Where to send the response. + + DISALLOW_COPY_AND_ASSIGN(LoginNotificationDetails); +}; + +// Details to provide the NotificationObserver. Used by the automation proxy +// for testing and by other LoginHandlers to dismiss themselves when an +// identical auth is supplied. +class AuthSuppliedLoginNotificationDetails : public LoginNotificationDetails { + public: + AuthSuppliedLoginNotificationDetails(LoginHandler* handler, + const std::wstring& username, + const std::wstring& password) + : LoginNotificationDetails(handler), + username_(username), + password_(password) {} + const std::wstring& username() const { return username_; } + const std::wstring& password() const { return password_; } + + private: + // The username that was used for the authentication. + const std::wstring username_; + + // The password that was used for the authentication. + const std::wstring password_; + + DISALLOW_COPY_AND_ASSIGN(AuthSuppliedLoginNotificationDetails); +}; + +// Prompts the user for their username and password. This is designed to +// be called on the background (I/O) thread, in response to +// net::URLRequest::Delegate::OnAuthRequired. The prompt will be created +// on the main UI thread via a call to UI loop's InvokeLater, and will send the +// credentials back to the net::URLRequest on the calling thread. +// A LoginHandler object (which lives on the calling thread) is returned, +// which can be used to set or cancel authentication programmatically. The +// caller must invoke OnRequestCancelled() on this LoginHandler before +// destroying the net::URLRequest. +LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info, + net::URLRequest* request); + +// Helper to remove the ref from an net::URLRequest to the LoginHandler. +// Should only be called from the IO thread, since it accesses an +// net::URLRequest. +void ResetLoginHandlerForRequest(net::URLRequest* request); + +// Get the signon_realm under which the identity should be saved. +std::string GetSignonRealm(const GURL& url, + const net::AuthChallengeInfo& auth_info); + +#endif // CHROME_BROWSER_UI_LOGIN_LOGIN_PROMPT_H_ diff --git a/chrome/browser/ui/login/login_prompt_gtk.cc b/chrome/browser/ui/login/login_prompt_gtk.cc new file mode 100644 index 0000000..92da74f --- /dev/null +++ b/chrome/browser/ui/login/login_prompt_gtk.cc @@ -0,0 +1,199 @@ +// Copyright (c) 2010 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 "chrome/browser/ui/login/login_prompt.h" + +#include <gtk/gtk.h> + +#include "app/l10n_util.h" +#include "app/gtk_signal.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/gtk/constrained_window_gtk.h" +#include "chrome/browser/gtk/gtk_util.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" +#include "chrome/browser/tab_contents/tab_contents_view_gtk.h" +#include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/ui/login/login_model.h" +#include "chrome/common/notification_service.h" +#include "grit/generated_resources.h" +#include "net/url_request/url_request.h" + +using webkit_glue::PasswordForm; + +// ---------------------------------------------------------------------------- +// LoginHandlerGtk + +// This class simply forwards the authentication from the LoginView (on +// the UI thread) to the net::URLRequest (on the I/O thread). +// This class uses ref counting to ensure that it lives until all InvokeLaters +// have been called. +class LoginHandlerGtk : public LoginHandler, + public ConstrainedWindowGtkDelegate { + public: + LoginHandlerGtk(net::AuthChallengeInfo* auth_info, net::URLRequest* request) + : LoginHandler(auth_info, request), + username_entry_(NULL), + password_entry_(NULL), + ok_(NULL) { + } + + virtual ~LoginHandlerGtk() { + root_.Destroy(); + } + + // LoginModelObserver implementation. + virtual void OnAutofillDataAvailable(const std::wstring& username, + const std::wstring& password) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // NOTE: Would be nice to use gtk_entry_get_text_length, but it is fairly + // new and not always in our GTK version. + if (strlen(gtk_entry_get_text(GTK_ENTRY(username_entry_))) == 0) { + gtk_entry_set_text(GTK_ENTRY(username_entry_), + WideToUTF8(username).c_str()); + gtk_entry_set_text(GTK_ENTRY(password_entry_), + WideToUTF8(password).c_str()); + gtk_editable_select_region(GTK_EDITABLE(username_entry_), 0, -1); + } + } + + // LoginHandler: + virtual void BuildViewForPasswordManager(PasswordManager* manager, + std::wstring explanation) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + root_.Own(gtk_vbox_new(FALSE, gtk_util::kContentAreaBorder)); + GtkWidget* label = gtk_label_new(WideToUTF8(explanation).c_str()); + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_box_pack_start(GTK_BOX(root_.get()), label, FALSE, FALSE, 0); + + username_entry_ = gtk_entry_new(); + gtk_entry_set_activates_default(GTK_ENTRY(username_entry_), TRUE); + + password_entry_ = gtk_entry_new(); + gtk_entry_set_activates_default(GTK_ENTRY(password_entry_), TRUE); + gtk_entry_set_visibility(GTK_ENTRY(password_entry_), FALSE); + + GtkWidget* table = gtk_util::CreateLabeledControlsGroup(NULL, + l10n_util::GetStringUTF8(IDS_LOGIN_DIALOG_USERNAME_FIELD).c_str(), + username_entry_, + l10n_util::GetStringUTF8(IDS_LOGIN_DIALOG_PASSWORD_FIELD).c_str(), + password_entry_, + NULL); + gtk_box_pack_start(GTK_BOX(root_.get()), table, FALSE, FALSE, 0); + + GtkWidget* hbox = gtk_hbox_new(FALSE, 12); + gtk_box_pack_start(GTK_BOX(root_.get()), hbox, FALSE, FALSE, 0); + + ok_ = gtk_button_new_from_stock(GTK_STOCK_OK); + gtk_button_set_label( + GTK_BUTTON(ok_), + l10n_util::GetStringUTF8(IDS_LOGIN_DIALOG_OK_BUTTON_LABEL).c_str()); + g_signal_connect(ok_, "clicked", G_CALLBACK(OnOKClickedThunk), this); + gtk_box_pack_end(GTK_BOX(hbox), ok_, FALSE, FALSE, 0); + + GtkWidget* cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL); + g_signal_connect(cancel, "clicked", G_CALLBACK(OnCancelClickedThunk), this); + gtk_box_pack_end(GTK_BOX(hbox), cancel, FALSE, FALSE, 0); + + g_signal_connect(root_.get(), "hierarchy-changed", + G_CALLBACK(OnPromptHierarchyChangedThunk), this); + + SetModel(manager); + + // Scary thread safety note: This can potentially be called *after* SetAuth + // or CancelAuth (say, if the request was cancelled before the UI thread got + // control). However, that's OK since any UI interaction in those functions + // will occur via an InvokeLater on the UI thread, which is guaranteed + // to happen after this is called (since this was InvokeLater'd first). + SetDialog(GetTabContentsForLogin()->CreateConstrainedDialog(this)); + + NotifyAuthNeeded(); + } + + // Overridden from ConstrainedWindowGtkDelegate: + virtual GtkWidget* GetWidgetRoot() { + return root_.get(); + } + + virtual void DeleteDelegate() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // The constrained window is going to delete itself; clear our pointer. + SetDialog(NULL); + SetModel(NULL); + + ReleaseSoon(); + } + + private: + friend class LoginPrompt; + + CHROMEGTK_CALLBACK_0(LoginHandlerGtk, void, OnOKClicked); + CHROMEGTK_CALLBACK_0(LoginHandlerGtk, void, OnCancelClicked); + CHROMEGTK_CALLBACK_1(LoginHandlerGtk, void, OnPromptHierarchyChanged, + GtkWidget*); + + // The GtkWidgets that form our visual hierarchy: + // The root container we pass to our parent. + OwnedWidgetGtk root_; + + // GtkEntry widgets that the user types into. + GtkWidget* username_entry_; + GtkWidget* password_entry_; + GtkWidget* ok_; + + DISALLOW_COPY_AND_ASSIGN(LoginHandlerGtk); +}; + +void LoginHandlerGtk::OnOKClicked(GtkWidget* sender) { + SetAuth( + UTF8ToWide(gtk_entry_get_text(GTK_ENTRY(username_entry_))), + UTF8ToWide(gtk_entry_get_text(GTK_ENTRY(password_entry_)))); +} + +void LoginHandlerGtk::OnCancelClicked(GtkWidget* sender) { + CancelAuth(); +} + +void LoginHandlerGtk::OnPromptHierarchyChanged(GtkWidget* sender, + GtkWidget* previous_toplevel) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (!GTK_WIDGET_TOPLEVEL(gtk_widget_get_toplevel(ok_))) + return; + + // Now that we have attached ourself to the window, we can make our OK + // button the default action and mess with the focus. + GTK_WIDGET_SET_FLAGS(ok_, GTK_CAN_DEFAULT); + gtk_widget_grab_default(ok_); + + TabContents* contents = GetTabContentsForLogin(); + + // The user may have focused another tab. In this case do not grab focus + // until this tab is refocused. + if ((!contents->delegate() || + contents->delegate()->ShouldFocusConstrainedWindow()) && + gtk_util::IsWidgetAncestryVisible(username_entry_)) { + gtk_widget_grab_focus(username_entry_); + } else { + // TODO(estade): this define should not need to be here because this class + // should not be used on linux/views. +#if defined(TOOLKIT_GTK) + static_cast<TabContentsViewGtk*>(contents->view())-> + SetFocusedWidget(username_entry_); +#endif + } +} + +// static +LoginHandler* LoginHandler::Create(net::AuthChallengeInfo* auth_info, + net::URLRequest* request) { + return new LoginHandlerGtk(auth_info, request); +} diff --git a/chrome/browser/ui/login/login_prompt_mac.h b/chrome/browser/ui/login/login_prompt_mac.h new file mode 100644 index 0000000..bf0677d --- /dev/null +++ b/chrome/browser/ui/login/login_prompt_mac.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_UI_LOGIN_LOGIN_PROMPT_MAC_H_ +#define CHROME_BROWSER_UI_LOGIN_LOGIN_PROMPT_MAC_H_ +#pragma once + +#import <Cocoa/Cocoa.h> + +class LoginHandlerMac; + +// Controller of the sheet used by LoginHandlerMac. Interface Builder wants +// this to be in a .h file. +@interface LoginHandlerSheet : NSWindowController { + @private + IBOutlet NSTextField* nameField_; + IBOutlet NSSecureTextField* passwordField_; + IBOutlet NSTextField* explanationField_; + IBOutlet NSButton* loginButton_; + IBOutlet NSButton* cancelButton_; + LoginHandlerMac* handler_; // weak, owns us +} +- (id)initWithLoginHandler:(LoginHandlerMac*)handler; +- (IBAction)loginPressed:(id)sender; +- (IBAction)cancelPressed:(id)sender; +- (void)sheetDidEnd:(NSWindow*)sheet + returnCode:(int)returnCode + contextInfo:(void*)contextInfo; +- (void)autofillLogin:(NSString*)login password:(NSString*)password; +- (void)setExplanation:(NSString*)explanation; +@end + +#endif // CHROME_BROWSER_UI_LOGIN_LOGIN_PROMPT_MAC_H_ diff --git a/chrome/browser/ui/login/login_prompt_mac.mm b/chrome/browser/ui/login/login_prompt_mac.mm new file mode 100644 index 0000000..d2f28d2 --- /dev/null +++ b/chrome/browser/ui/login/login_prompt_mac.mm @@ -0,0 +1,191 @@ +// Copyright (c) 2009 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 "chrome/browser/ui/login/login_prompt.h" +#import "chrome/browser/ui/login/login_prompt_mac.h" + +#include "app/l10n_util.h" +#include "base/mac_util.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/ui/cocoa/constrained_window_mac.h" +#include "chrome/browser/ui/login/login_model.h" +#include "chrome/common/notification_service.h" +#include "grit/generated_resources.h" +#include "net/url_request/url_request.h" +#include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h" + +using webkit_glue::PasswordForm; + +// ---------------------------------------------------------------------------- +// LoginHandlerMac + +// This class simply forwards the authentication from the LoginView (on +// the UI thread) to the net::URLRequest (on the I/O thread). +// This class uses ref counting to ensure that it lives until all InvokeLaters +// have been called. +class LoginHandlerMac : public LoginHandler, + public ConstrainedWindowMacDelegateCustomSheet { + public: + LoginHandlerMac(net::AuthChallengeInfo* auth_info, net::URLRequest* request) + : LoginHandler(auth_info, request), + sheet_controller_(nil) { + } + + virtual ~LoginHandlerMac() { + } + + // LoginModelObserver implementation. + virtual void OnAutofillDataAvailable(const std::wstring& username, + const std::wstring& password) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + [sheet_controller_ autofillLogin:base::SysWideToNSString(username) + password:base::SysWideToNSString(password)]; + } + + // LoginHandler: + virtual void BuildViewForPasswordManager(PasswordManager* manager, + std::wstring explanation) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // Load nib here instead of in constructor. + sheet_controller_ = [[[LoginHandlerSheet alloc] + initWithLoginHandler:this] autorelease]; + init([sheet_controller_ window], sheet_controller_, + @selector(sheetDidEnd:returnCode:contextInfo:)); + + SetModel(manager); + + [sheet_controller_ setExplanation:base::SysWideToNSString(explanation)]; + + // Scary thread safety note: This can potentially be called *after* SetAuth + // or CancelAuth (say, if the request was cancelled before the UI thread got + // control). However, that's OK since any UI interaction in those functions + // will occur via an InvokeLater on the UI thread, which is guaranteed + // to happen after this is called (since this was InvokeLater'd first). + SetDialog(GetTabContentsForLogin()->CreateConstrainedDialog(this)); + + NotifyAuthNeeded(); + } + + // Overridden from ConstrainedWindowMacDelegate: + virtual void DeleteDelegate() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // The constrained window is going to delete itself; clear our pointer. + SetDialog(NULL); + SetModel(NULL); + + // Close sheet if it's still open, as required by + // ConstrainedWindowMacDelegate. + if (is_sheet_open()) + [NSApp endSheet:sheet()]; + + ReleaseSoon(); + } + + void OnLoginPressed(const std::wstring& username, + const std::wstring& password) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + SetAuth(username, password); + } + + void OnCancelPressed() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + CancelAuth(); + } + + private: + friend class LoginPrompt; + + // The Cocoa controller of the GUI. + LoginHandlerSheet* sheet_controller_; + + DISALLOW_COPY_AND_ASSIGN(LoginHandlerMac); +}; + +// static +LoginHandler* LoginHandler::Create(net::AuthChallengeInfo* auth_info, + net::URLRequest* request) { + return new LoginHandlerMac(auth_info, request); +} + +// ---------------------------------------------------------------------------- +// LoginHandlerSheet + +@implementation LoginHandlerSheet + +- (id)initWithLoginHandler:(LoginHandlerMac*)handler { + NSString* nibPath = + [mac_util::MainAppBundle() pathForResource:@"HttpAuthLoginSheet" + ofType:@"nib"]; + if ((self = [super initWithWindowNibPath:nibPath + owner:self])) { + handler_ = handler; + } + return self; +} + +- (void)dealloc { + // The buttons could be in a modal loop, so disconnect them so they cannot + // call back to us after we're dead. + [loginButton_ setTarget:nil]; + [cancelButton_ setTarget:nil]; + [super dealloc]; +} + +- (IBAction)loginPressed:(id)sender { + using base::SysNSStringToWide; + [NSApp endSheet:[self window]]; + handler_->OnLoginPressed(SysNSStringToWide([nameField_ stringValue]), + SysNSStringToWide([passwordField_ stringValue])); +} + +- (IBAction)cancelPressed:(id)sender { + [NSApp endSheet:[self window]]; + handler_->OnCancelPressed(); +} + +- (void)sheetDidEnd:(NSWindow*)sheet + returnCode:(int)returnCode + contextInfo:(void *)contextInfo { + [sheet orderOut:self]; + // Also called when user navigates to another page while the sheet is open. +} + +- (void)autofillLogin:(NSString*)login password:(NSString*)password { + if ([[nameField_ stringValue] length] == 0) { + [nameField_ setStringValue:login]; + [passwordField_ setStringValue:password]; + [nameField_ selectText:self]; + } +} + +- (void)setExplanation:(NSString*)explanation { + // Put in the text. + [explanationField_ setStringValue:explanation]; + + // Resize the TextField. + CGFloat explanationShift = + [GTMUILocalizerAndLayoutTweaker + sizeToFitFixedWidthTextField:explanationField_]; + + // Resize the window (no shifting needed due to window layout). + NSSize windowDelta = NSMakeSize(0, explanationShift); + [GTMUILocalizerAndLayoutTweaker + resizeWindowWithoutAutoResizingSubViews:[self window] + delta:windowDelta]; +} + +@end diff --git a/chrome/browser/ui/login/login_prompt_uitest.cc b/chrome/browser/ui/login/login_prompt_uitest.cc new file mode 100644 index 0000000..bdda3fd --- /dev/null +++ b/chrome/browser/ui/login/login_prompt_uitest.cc @@ -0,0 +1,280 @@ +// Copyright (c) 2006-2008 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 <string> + +#include "chrome/browser/net/url_fixer_upper.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/ui/ui_test.h" +#include "net/test/test_server.h" + +using std::wstring; + +namespace { + +const FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data"); + +} // namespace + +class LoginPromptTest : public UITest { + protected: + LoginPromptTest() + : username_basic_(L"basicuser"), + username_digest_(L"digestuser"), + password_(L"secret"), + password_bad_(L"denyme"), + test_server_(net::TestServer::TYPE_HTTP, FilePath(kDocRoot)) { + } + + void AppendTab(const GURL& url) { + scoped_refptr<BrowserProxy> window_proxy(automation()->GetBrowserWindow(0)); + ASSERT_TRUE(window_proxy.get()); + ASSERT_TRUE(window_proxy->AppendTab(url)); + } + + protected: + wstring username_basic_; + wstring username_digest_; + wstring password_; + wstring password_bad_; + + net::TestServer test_server_; +}; + +wstring ExpectedTitleFromAuth(const wstring& username, + const wstring& password) { + // The TestServer sets the title to username/password on successful login. + return username + L"/" + password; +} + +#if defined(OS_WIN) +// Probably related to test server flakiness in http://crbug.com/60937 +#define MAYBE_TestBasicAuth FLAKY_TestBasicAuth +#else +#define MAYBE_TestBasicAuth TestBasicAuth +#endif + +// Test that "Basic" HTTP authentication works. +TEST_F(LoginPromptTest, MAYBE_TestBasicAuth) { + ASSERT_TRUE(test_server_.Start()); + + scoped_refptr<TabProxy> tab(GetActiveTab()); + ASSERT_TRUE(tab.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-basic"))); + + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_FALSE(tab->SetAuth(username_basic_, password_bad_)); + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_TRUE(tab->CancelAuth()); + EXPECT_EQ(L"Denied: wrong password", GetActiveTabTitle()); + + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-basic"))); + + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_TRUE(tab->SetAuth(username_basic_, password_)); + EXPECT_EQ(ExpectedTitleFromAuth(username_basic_, password_), + GetActiveTabTitle()); +} + +#if defined(OS_WIN) +// Probably related to test server flakiness in http://crbug.com/60937 +#define MAYBE_TestDigestAuth FLAKY_TestDigestAuth +#else +#define MAYBE_TestDigestAuth TestDigestAuth +#endif + +// Test that "Digest" HTTP authentication works. +TEST_F(LoginPromptTest, MAYBE_TestDigestAuth) { + ASSERT_TRUE(test_server_.Start()); + + scoped_refptr<TabProxy> tab(GetActiveTab()); + ASSERT_TRUE(tab.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-digest"))); + + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_FALSE(tab->SetAuth(username_digest_, password_bad_)); + EXPECT_TRUE(tab->CancelAuth()); + EXPECT_EQ(L"Denied: wrong password", GetActiveTabTitle()); + + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-digest"))); + + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_TRUE(tab->SetAuth(username_digest_, password_)); + EXPECT_EQ(ExpectedTitleFromAuth(username_digest_, password_), + GetActiveTabTitle()); +} + +#if defined(OS_WIN) +// Probably related to test server flakiness in http://crbug.com/60937 +#define MAYBE_TestTwoAuths FLAKY_TestTwoAuths +#else +#define MAYBE_TestTwoAuths TestTwoAuths +#endif + +// Test that logging in on 2 tabs at once works. +TEST_F(LoginPromptTest, MAYBE_TestTwoAuths) { + ASSERT_TRUE(test_server_.Start()); + + scoped_refptr<TabProxy> basic_tab(GetActiveTab()); + ASSERT_TRUE(basic_tab.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + basic_tab->NavigateToURL(test_server_.GetURL("auth-basic"))); + + AppendTab(GURL(chrome::kAboutBlankURL)); + scoped_refptr<TabProxy> digest_tab(GetActiveTab()); + ASSERT_TRUE(digest_tab.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + digest_tab->NavigateToURL(test_server_.GetURL("auth-digest"))); + + EXPECT_TRUE(basic_tab->NeedsAuth()); + EXPECT_TRUE(basic_tab->SetAuth(username_basic_, password_)); + EXPECT_TRUE(digest_tab->NeedsAuth()); + EXPECT_TRUE(digest_tab->SetAuth(username_digest_, password_)); + + wstring title; + EXPECT_TRUE(basic_tab->GetTabTitle(&title)); + EXPECT_EQ(ExpectedTitleFromAuth(username_basic_, password_), title); + + EXPECT_TRUE(digest_tab->GetTabTitle(&title)); + EXPECT_EQ(ExpectedTitleFromAuth(username_digest_, password_), title); +} + +#if defined(OS_WIN) +// Probably related to test server flakiness in http://crbug.com/60937 +#define MAYBE_TestCancelAuth FLAKY_TestCancelAuth +#else +#define MAYBE_TestCancelAuth TestCancelAuth +#endif + +// Test that cancelling authentication works. +TEST_F(LoginPromptTest, MAYBE_TestCancelAuth) { + ASSERT_TRUE(test_server_.Start()); + + scoped_refptr<TabProxy> tab(GetActiveTab()); + ASSERT_TRUE(tab.get()); + + // First navigate to a test server page so we have something to go back to. + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(test_server_.GetURL("a"))); + + // Navigating while auth is requested is the same as cancelling. + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-basic"))); + EXPECT_TRUE(tab->NeedsAuth()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(test_server_.GetURL("b"))); + EXPECT_FALSE(tab->NeedsAuth()); + + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-basic"))); + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_TRUE(tab->GoBack()); // should bring us back to 'a' + EXPECT_FALSE(tab->NeedsAuth()); + + // Now add a page and go back, so we have something to go forward to. + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS, + tab->NavigateToURL(test_server_.GetURL("c"))); + EXPECT_TRUE(tab->GoBack()); // should bring us back to 'a' + + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-basic"))); + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_TRUE(tab->GoForward()); // should bring us to 'c' + EXPECT_FALSE(tab->NeedsAuth()); + + // Now test that cancelling works as expected. + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + tab->NavigateToURL(test_server_.GetURL("auth-basic"))); + EXPECT_TRUE(tab->NeedsAuth()); + EXPECT_TRUE(tab->CancelAuth()); + EXPECT_FALSE(tab->NeedsAuth()); + EXPECT_EQ(L"Denied: no auth", GetActiveTabTitle()); +} + +#if defined(OS_WIN) +// Probably related to test server flakiness in http://crbug.com/60937 +#define MAYBE_SupplyRedundantAuths FLAKY_SupplyRedundantAuths +#else +#define MAYBE_SupplyRedundantAuths SupplyRedundantAuths +#endif + +// If multiple tabs are looking for the same auth, the user should only have to +// enter it once. +TEST_F(LoginPromptTest, MAYBE_SupplyRedundantAuths) { + ASSERT_TRUE(test_server_.Start()); + + scoped_refptr<TabProxy> basic_tab1(GetActiveTab()); + ASSERT_TRUE(basic_tab1.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + basic_tab1->NavigateToURL(test_server_.GetURL("auth-basic/1"))); + EXPECT_TRUE(basic_tab1->NeedsAuth()); + + AppendTab(GURL(chrome::kAboutBlankURL)); + scoped_refptr<TabProxy> basic_tab2(GetActiveTab()); + ASSERT_TRUE(basic_tab2.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + basic_tab2->NavigateToURL(test_server_.GetURL("auth-basic/2"))); + EXPECT_TRUE(basic_tab2->NeedsAuth()); + + // Set the auth in only one of the tabs (but wait for the other to load). + int64 last_navigation_time; + ASSERT_TRUE(basic_tab2->GetLastNavigationTime(&last_navigation_time)); + EXPECT_TRUE(basic_tab1->SetAuth(username_basic_, password_)); + EXPECT_TRUE(basic_tab2->WaitForNavigation(last_navigation_time)); + + // Now both tabs have loaded. + wstring title1; + EXPECT_TRUE(basic_tab1->GetTabTitle(&title1)); + EXPECT_EQ(ExpectedTitleFromAuth(username_basic_, password_), title1); + wstring title2; + EXPECT_TRUE(basic_tab2->GetTabTitle(&title2)); + EXPECT_EQ(ExpectedTitleFromAuth(username_basic_, password_), title2); +} + +#if defined(OS_WIN) +// Probably related to test server flakiness in http://crbug.com/60937 +#define MAYBE_CancelRedundantAuths FLAKY_CancelRedundantAuths +#else +#define MAYBE_CancelRedundantAuths CancelRedundantAuths +#endif + +// If multiple tabs are looking for the same auth, and one is cancelled, the +// other should be cancelled as well. +TEST_F(LoginPromptTest, MAYBE_CancelRedundantAuths) { + ASSERT_TRUE(test_server_.Start()); + + scoped_refptr<TabProxy> basic_tab1(GetActiveTab()); + ASSERT_TRUE(basic_tab1.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + basic_tab1->NavigateToURL(test_server_.GetURL("auth-basic/1"))); + EXPECT_TRUE(basic_tab1->NeedsAuth()); + + AppendTab(GURL(chrome::kAboutBlankURL)); + scoped_refptr<TabProxy> basic_tab2(GetActiveTab()); + ASSERT_TRUE(basic_tab2.get()); + ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED, + basic_tab2->NavigateToURL(test_server_.GetURL("auth-basic/2"))); + EXPECT_TRUE(basic_tab2->NeedsAuth()); + + // Cancel the auth in only one of the tabs (but wait for the other to load). + int64 last_navigation_time; + ASSERT_TRUE(basic_tab2->GetLastNavigationTime(&last_navigation_time)); + EXPECT_TRUE(basic_tab1->CancelAuth()); + EXPECT_TRUE(basic_tab2->WaitForNavigation(last_navigation_time)); + + // Now both tabs have been denied. + wstring title1; + EXPECT_TRUE(basic_tab1->GetTabTitle(&title1)); + EXPECT_EQ(L"Denied: no auth", title1); + wstring title2; + EXPECT_TRUE(basic_tab2->GetTabTitle(&title2)); + EXPECT_EQ(L"Denied: no auth", title2); +} diff --git a/chrome/browser/ui/login/login_prompt_unittest.cc b/chrome/browser/ui/login/login_prompt_unittest.cc new file mode 100644 index 0000000..9454e13 --- /dev/null +++ b/chrome/browser/ui/login/login_prompt_unittest.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2006-2008 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 "chrome/browser/ui/login/login_prompt.h" +#include "googleurl/src/gurl.h" +#include "net/base/auth.h" +#include "testing/gtest/include/gtest/gtest.h" + + +TEST(LoginPromptTest, GetSignonRealm) { + scoped_refptr<net::AuthChallengeInfo> auth_info = new net::AuthChallengeInfo; + auth_info->is_proxy = false; // server auth + // auth_info->host is intentionally left empty. + auth_info->scheme = L"Basic"; + auth_info->realm = L"WallyWorld"; + + std::string url[] = { + "https://www.nowhere.org/dir/index.html", + "https://www.nowhere.org:443/dir/index.html", // default port + "https://www.nowhere.org:8443/dir/index.html", // non-default port + "https://www.nowhere.org", // no trailing slash + "https://foo:bar@www.nowhere.org/dir/index.html", // username:password + "https://www.nowhere.org/dir/index.html?id=965362", // query + "https://www.nowhere.org/dir/index.html#toc", // reference + }; + + std::string expected[] = { + "https://www.nowhere.org/WallyWorld", + "https://www.nowhere.org/WallyWorld", + "https://www.nowhere.org:8443/WallyWorld", + "https://www.nowhere.org/WallyWorld", + "https://www.nowhere.org/WallyWorld", + "https://www.nowhere.org/WallyWorld", + "https://www.nowhere.org/WallyWorld" + }; + + for (size_t i = 0; i < arraysize(url); i++) { + std::string key = GetSignonRealm(GURL(url[i]), *auth_info); + EXPECT_EQ(expected[i], key); + } +} diff --git a/chrome/browser/ui/login/login_prompt_win.cc b/chrome/browser/ui/login/login_prompt_win.cc new file mode 100644 index 0000000..d73954b --- /dev/null +++ b/chrome/browser/ui/login/login_prompt_win.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2009 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 "chrome/browser/ui/login/login_prompt.h" + +#include "app/l10n_util.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" +#include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/ui/views/login_view.h" +#include "chrome/common/notification_service.h" +#include "grit/generated_resources.h" +#include "net/url_request/url_request.h" +#include "views/window/dialog_delegate.h" + +using webkit_glue::PasswordForm; + +// ---------------------------------------------------------------------------- +// LoginHandlerWin + +// This class simply forwards the authentication from the LoginView (on +// the UI thread) to the net::URLRequest (on the I/O thread). +// This class uses ref counting to ensure that it lives until all InvokeLaters +// have been called. +class LoginHandlerWin : public LoginHandler, + public ConstrainedDialogDelegate { + public: + LoginHandlerWin(net::AuthChallengeInfo* auth_info, net::URLRequest* request) + : LoginHandler(auth_info, request) { + } + + // LoginModelObserver implementation. + virtual void OnAutofillDataAvailable(const std::wstring& username, + const std::wstring& password) { + // Nothing to do here since LoginView takes care of autofil for win. + } + + void set_login_view(LoginView* login_view) { + login_view_ = login_view; + } + + // views::DialogDelegate methods: + virtual std::wstring GetDialogButtonLabel( + MessageBoxFlags::DialogButton button) const { + if (button == MessageBoxFlags::DIALOGBUTTON_OK) + return l10n_util::GetString(IDS_LOGIN_DIALOG_OK_BUTTON_LABEL); + return DialogDelegate::GetDialogButtonLabel(button); + } + + virtual std::wstring GetWindowTitle() const { + return l10n_util::GetString(IDS_LOGIN_DIALOG_TITLE); + } + + virtual void WindowClosing() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + TabContents* tab = GetTabContentsForLogin(); + if (tab) + tab->render_view_host()->set_ignore_input_events(false); + + // Reference is no longer valid. + SetDialog(NULL); + + CancelAuth(); + } + + virtual void DeleteDelegate() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // The constrained window is going to delete itself; clear our pointer. + SetDialog(NULL); + SetModel(NULL); + + ReleaseSoon(); + } + + virtual bool Cancel() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + CancelAuth(); + return true; + } + + virtual bool Accept() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + SetAuth(login_view_->GetUsername(), login_view_->GetPassword()); + return true; + } + + virtual views::View* GetContentsView() { + return login_view_; + } + + // LoginHandler: + + virtual void BuildViewForPasswordManager(PasswordManager* manager, + std::wstring explanation) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + TabContents* tab_contents = GetTabContentsForLogin(); + bool should_focus_view = !tab_contents->delegate() || + tab_contents->delegate()->ShouldFocusConstrainedWindow(); + + LoginView* view = new LoginView(explanation, should_focus_view); + + // Set the model for the login view. The model (password manager) is owned + // by the view's parent TabContents, so natural destruction order means we + // don't have to worry about calling SetModel(NULL), because the view will + // be deleted before the password manager. + view->SetModel(manager); + + set_login_view(view); + + // Scary thread safety note: This can potentially be called *after* SetAuth + // or CancelAuth (say, if the request was cancelled before the UI thread got + // control). However, that's OK since any UI interaction in those functions + // will occur via an InvokeLater on the UI thread, which is guaranteed + // to happen after this is called (since this was InvokeLater'd first). + SetDialog(GetTabContentsForLogin()->CreateConstrainedDialog(this)); + NotifyAuthNeeded(); + } + + private: + friend class base::RefCountedThreadSafe<LoginHandlerWin>; + friend class LoginPrompt; + + ~LoginHandlerWin() {} + + // The LoginView that contains the user's login information + LoginView* login_view_; + + DISALLOW_COPY_AND_ASSIGN(LoginHandlerWin); +}; + +// static +LoginHandler* LoginHandler::Create(net::AuthChallengeInfo* auth_info, + net::URLRequest* request) { + return new LoginHandlerWin(auth_info, request); +} |