// 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/login_prompt.h" #include #include "app/l10n_util.h" #include "base/command_line.h" #include "base/lock.h" #include "base/utf_string_conversions.h" #include "chrome/browser/chrome_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/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 URLRequest to the LoginHandler. // Should only be called from the IO thread, since it accesses an URLRequest. void ResetLoginHandlerForRequest(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, 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(). ChromeThread::PostTask( ChromeThread::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(ChromeThread::CurrentlyOn(ChromeThread::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_); } ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred)); ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod( this, &LoginHandler::NotifyAuthSupplied, username, password)); ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod( this, &LoginHandler::SetAuthDeferred, username, password)); } void LoginHandler::CancelAuth() { if (WasAuthHandled(true)) return; ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred)); ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled)); ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred)); } void LoginHandler::OnRequestCancelled() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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(ChromeThread::CurrentlyOn(ChromeThread::UI)); registrar_.Add(this, NotificationType::AUTH_SUPPLIED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::AUTH_CANCELLED, NotificationService::AllSources()); } void LoginHandler::RemoveObservers() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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(ChromeThread::CurrentlyOn(ChromeThread::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(source).ptr(); // Only handle notifications from other handlers. if (this_controller == that_controller) return; LoginNotificationDetails* login_details = Details(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(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(ChromeThread::CurrentlyOn(ChromeThread::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(controller), Details(&details)); } void LoginHandler::NotifyAuthCancelled() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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(controller), Details(&details)); } void LoginHandler::NotifyAuthSupplied(const std::wstring& username, const std::wstring& password) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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(controller), Details(&details)); } void LoginHandler::ReleaseSoon() { if (!WasAuthHandled(true)) { ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred)); ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled)); } ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandler::RemoveObservers)); // Delete this object once all InvokeLaters have been called. ChromeThread::ReleaseSoon(ChromeThread::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(ChromeThread::CurrentlyOn(ChromeThread::IO)); if (request_) { request_->SetAuth(WideToUTF16Hack(username), WideToUTF16Hack(password)); ResetLoginHandlerForRequest(request_); } } // Calls CancelAuth from the IO loop. void LoginHandler::CancelAuthDeferred() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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(ChromeThread::CurrentlyOn(ChromeThread::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 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. PasswordManager* password_manager = parent_contents->GetPasswordManager(); std::vector 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* 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 URLRequest initiating the auth challenge. GURL request_url_; // Info about who/where/what is asking for authentication. scoped_refptr 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, URLRequest* request) { LoginHandler* handler = LoginHandler::Create(auth_info, request); ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, new LoginDialogTask( request->url(), auth_info, handler)); return handler; }