// 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" #import "chrome/browser/login_prompt_mac.h" #include "app/l10n_util.h" #include "base/mac_util.h" #include "base/message_loop.h" #include "base/sys_string_conversions.h" #include "chrome/browser/cocoa/constrained_window_mac.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/notification_service.h" #include "grit/generated_resources.h" #include "net/url_request/url_request.h" using webkit_glue::PasswordForm; // ---------------------------------------------------------------------------- // LoginHandlerMac // 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 LoginHandlerMac : public LoginHandler, public base::RefCountedThreadSafe, public ConstrainedWindowMacDelegateCustomSheet { public: LoginHandlerMac(URLRequest* request, MessageLoop* ui_loop) : handled_auth_(false), dialog_(NULL), ui_loop_(ui_loop), request_(request), request_loop_(MessageLoop::current()), password_manager_(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_) << "LoginHandlerMac constructed with NULL request"; AddRef(); // matched by ReleaseLater. if (!ResourceDispatcherHost::RenderViewForRequest(request_, &render_process_host_id_, &tab_contents_id_)) { NOTREACHED(); } } virtual ~LoginHandlerMac() { } // LoginHandler: virtual void BuildViewForPasswordManager(PasswordManager* manager, std::wstring explanation) { DCHECK(MessageLoop::current() == ui_loop_); // Load nib here instead of in constructor. LoginHandlerSheet* sheetController = [[[LoginHandlerSheet alloc] initWithLoginHandler:this] autorelease]; init([sheetController window], sheetController, @selector(sheetDidEnd:returnCode:contextInfo:)); // 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); 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(MessageLoop::current() == ui_loop_); 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 = username; password_form_.password_value = password; password_manager_->ProvisionallySavePassword(password_form_); } ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::CloseContentsDeferred)); ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::SendNotifications)); request_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::SetAuthDeferred, username, password)); } virtual void CancelAuth() { if (WasAuthHandled(true)) return; ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::CloseContentsDeferred)); ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::SendNotifications)); request_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::CancelAuthDeferred)); } virtual void OnRequestCancelled() { DCHECK(MessageLoop::current() == request_loop_) << "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 ConstrainedWindowMacDelegate: virtual void DeleteDelegate() { if (!WasAuthHandled(true)) { request_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::CancelAuthDeferred)); ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &LoginHandlerMac::SendNotifications)); } // Delete this object once all InvokeLaters have been called. request_loop_->ReleaseSoon(FROM_HERE, this); } void OnLoginPressed(const std::wstring& username, const std::wstring& password) { DCHECK(MessageLoop::current() == ui_loop_); SetAuth(username, password); } void OnCancelPressed() { DCHECK(MessageLoop::current() == ui_loop_); CancelAuth(); } private: friend class LoginPrompt; // Calls SetAuth from the request_loop. void SetAuthDeferred(const std::wstring& username, const std::wstring& password) { DCHECK(MessageLoop::current() == request_loop_); if (request_) { request_->SetAuth(username, password); ResetLoginHandlerForRequest(request_); } } // Calls CancelAuth from the request_loop. void CancelAuthDeferred() { DCHECK(MessageLoop::current() == request_loop_); 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(MessageLoop::current() == ui_loop_); // Close sheet if it's still open, as required by // ConstrainedWindowMacDelegate. if (is_sheet_open()) [NSApp endSheet:sheet()]; // 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(MessageLoop::current() == ui_loop_); 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()); } } // 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 MessageLoop of the thread that the ChromeViewContents lives in. MessageLoop* ui_loop_; // The request that wants login data. // This should only be accessed on the request_loop_. URLRequest* request_; // The MessageLoop of the thread that the URLRequest lives in. MessageLoop* request_loop_; // 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_; DISALLOW_COPY_AND_ASSIGN(LoginHandlerMac); }; // static LoginHandler* LoginHandler::Create(URLRequest* request, MessageLoop* ui_loop) { return new LoginHandlerMac(request, ui_loop); } // ---------------------------------------------------------------------------- // LoginHandlerSheet @implementation LoginHandlerSheet - (id)initWithLoginHandler:(LoginHandlerMac*)handler { if ((self = [super initWithWindowNibName:@"HttpAuthLoginSheet" owner:self])) { handler_ = handler; } return self; } - (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. } @end