// 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 "chrome/browser/chrome_thread.h" #include "chrome/browser/gtk/constrained_window_gtk.h" #include "chrome/browser/login_model.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/common/gtk_util.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 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 base::RefCountedThreadSafe, public ConstrainedWindowGtkDelegate, public LoginModelObserver { public: explicit LoginHandlerGtk(URLRequest* request) : handled_auth_(false), dialog_(NULL), request_(request), password_manager_(NULL), login_model_(NULL) { DCHECK(request_) << "LoginHandlerGtk constructed with NULL request"; AddRef(); // matched by ReleaseLater. if (!ResourceDispatcherHost::RenderViewForRequest(request_, &render_process_host_id_, &tab_contents_id_)) { NOTREACHED(); } } virtual ~LoginHandlerGtk() { if (login_model_) login_model_->SetObserver(NULL); root_.Destroy(); } void SetModel(LoginModel* model) { if (login_model_) login_model_->SetObserver(NULL); login_model_ = model; if (login_model_) login_model_->SetObserver(this); } // LoginModelObserver implementation. virtual void OnAutofillDataAvailable(const std::wstring& username, const std::wstring& password) { // 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(ChromeThread::CurrentlyOn(ChromeThread::UI)); root_.Own(gtk_vbox_new(NULL, 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); GtkWidget* 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(OnOKClicked), 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(OnCancelClicked), this); gtk_box_pack_end(GTK_BOX(hbox), cancel, FALSE, FALSE, 0); 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). dialog_ = GetTabContentsForLogin()->CreateConstrainedDialog(this); // 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); gtk_widget_grab_focus(username_entry_); SendNotifications(); } virtual void SetPasswordForm(const webkit_glue::PasswordForm& form) { password_form_ = form; } virtual void SetPasswordManager(PasswordManager* password_manager) { password_manager_ = password_manager; } virtual TabContents* GetTabContentsForLogin() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); return tab_util::GetTabContentsByID(render_process_host_id_, tab_contents_id_); } virtual void 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, &LoginHandlerGtk::CloseContentsDeferred)); ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandlerGtk::SendNotifications)); ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &LoginHandlerGtk::SetAuthDeferred, username, password)); } virtual void CancelAuth() { if (WasAuthHandled(true)) return; ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandlerGtk::CloseContentsDeferred)); ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandlerGtk::SendNotifications)); ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &LoginHandlerGtk::CancelAuthDeferred)); } virtual void 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(); } // Overridden from ConstrainedWindowGtkDelegate: virtual GtkWidget* GetWidgetRoot() { return root_.get(); } virtual void DeleteDelegate() { if (!WasAuthHandled(true)) { ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableMethod(this, &LoginHandlerGtk::CancelAuthDeferred)); ChromeThread::PostTask( ChromeThread::UI, FROM_HERE, NewRunnableMethod(this, &LoginHandlerGtk::SendNotifications)); } // The constrained window is going to delete itself; clear our pointer. dialog_ = NULL; SetModel(NULL); // Delete this object once all InvokeLaters have been called. ChromeThread::ReleaseSoon(ChromeThread::IO, FROM_HERE, this); } private: friend class LoginPrompt; // Calls SetAuth from the IO loop. void SetAuthDeferred(const std::wstring& username, const std::wstring& password) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); if (request_) { request_->SetAuth(username, password); ResetLoginHandlerForRequest(request_); } } // Calls CancelAuth from the IO loop. void CancelAuthDeferred() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); if (request_) { request_->CancelAuth(); // Verify that CancelAuth does destroy the request via our delegate. DCHECK(request_ != NULL); ResetLoginHandlerForRequest(request_); } } // Closes the view_contents from the UI loop. void CloseContentsDeferred() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); // The hosting ConstrainedWindow may have been freed. if (dialog_) dialog_->CloseConstrainedWindow(); } // 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) { AutoLock lock(handled_auth_lock_); bool was_handled = handled_auth_; if (set_handled) handled_auth_ = true; return was_handled; } // Notify observers that authentication is needed or received. The automation // proxy uses this for testing. void SendNotifications() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); NotificationService* service = NotificationService::current(); TabContents* requesting_contents = GetTabContentsForLogin(); if (!requesting_contents) return; NavigationController* controller = &requesting_contents->controller(); if (!WasAuthHandled(false)) { LoginNotificationDetails details(this); service->Notify(NotificationType::AUTH_NEEDED, Source(controller), Details(&details)); } else { service->Notify(NotificationType::AUTH_SUPPLIED, Source(controller), NotificationService::NoDetails()); } } static void OnOKClicked(GtkButton *button, LoginHandlerGtk* handler) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); handler->SetAuth( UTF8ToWide(gtk_entry_get_text(GTK_ENTRY(handler->username_entry_))), UTF8ToWide(gtk_entry_get_text(GTK_ENTRY(handler->password_entry_)))); } static void OnCancelClicked(GtkButton *button, LoginHandlerGtk* handler) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); handler->CancelAuth(); } // 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_; // The request that wants login data. // This should only be accessed on the IO loop. 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. 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 URLRequest, in case it goes NULL on us. int render_process_host_id_; int tab_contents_id_; // 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_; // 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_; DISALLOW_COPY_AND_ASSIGN(LoginHandlerGtk); }; // static LoginHandler* LoginHandler::Create(URLRequest* request) { return new LoginHandlerGtk(request); }