Home | History | Annotate | Download | only in login
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/login/login_prompt.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/command_line.h"
     10 #include "base/synchronization/lock.h"
     11 #include "base/utf_string_conversions.h"
     12 #include "chrome/browser/password_manager/password_manager.h"
     13 #include "chrome/browser/tab_contents/tab_util.h"
     14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     15 #include "content/browser/browser_thread.h"
     16 #include "content/browser/renderer_host/render_process_host.h"
     17 #include "content/browser/renderer_host/render_view_host.h"
     18 #include "content/browser/renderer_host/render_view_host_delegate.h"
     19 #include "content/browser/renderer_host/resource_dispatcher_host.h"
     20 #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h"
     21 #include "content/browser/tab_contents/constrained_window.h"
     22 #include "content/browser/tab_contents/tab_contents.h"
     23 #include "content/common/notification_service.h"
     24 #include "grit/generated_resources.h"
     25 #include "net/base/auth.h"
     26 #include "net/base/net_util.h"
     27 #include "net/url_request/url_request.h"
     28 #include "ui/base/l10n/l10n_util.h"
     29 
     30 using webkit_glue::PasswordForm;
     31 
     32 class LoginHandlerImpl;
     33 
     34 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
     35 // Should only be called from the IO thread, since it accesses an
     36 // net::URLRequest.
     37 void ResetLoginHandlerForRequest(net::URLRequest* request) {
     38   ResourceDispatcherHostRequestInfo* info =
     39       ResourceDispatcherHost::InfoForRequest(request);
     40   if (!info)
     41     return;
     42 
     43   info->set_login_handler(NULL);
     44 }
     45 
     46 // Get the signon_realm under which this auth info should be stored.
     47 //
     48 // The format of the signon_realm for proxy auth is:
     49 //     proxy-host/auth-realm
     50 // The format of the signon_realm for server auth is:
     51 //     url-scheme://url-host[:url-port]/auth-realm
     52 //
     53 // Be careful when changing this function, since you could make existing
     54 // saved logins un-retrievable.
     55 std::string GetSignonRealm(const GURL& url,
     56                            const net::AuthChallengeInfo& auth_info) {
     57   std::string signon_realm;
     58   if (auth_info.is_proxy) {
     59     signon_realm = WideToASCII(auth_info.host_and_port);
     60     signon_realm.append("/");
     61   } else {
     62     // Take scheme, host, and port from the url.
     63     signon_realm = url.GetOrigin().spec();
     64     // This ends with a "/".
     65   }
     66   signon_realm.append(WideToUTF8(auth_info.realm));
     67   return signon_realm;
     68 }
     69 
     70 // ----------------------------------------------------------------------------
     71 // LoginHandler
     72 
     73 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
     74                            net::URLRequest* request)
     75     : handled_auth_(false),
     76       dialog_(NULL),
     77       auth_info_(auth_info),
     78       request_(request),
     79       password_manager_(NULL),
     80       login_model_(NULL) {
     81   // This constructor is called on the I/O thread, so we cannot load the nib
     82   // here. BuildViewForPasswordManager() will be invoked on the UI thread
     83   // later, so wait with loading the nib until then.
     84   DCHECK(request_) << "LoginHandler constructed with NULL request";
     85   DCHECK(auth_info_) << "LoginHandler constructed with NULL auth info";
     86 
     87   AddRef();  // matched by LoginHandler::ReleaseSoon().
     88 
     89   BrowserThread::PostTask(
     90       BrowserThread::UI, FROM_HERE,
     91       NewRunnableMethod(this, &LoginHandler::AddObservers));
     92 
     93   if (!ResourceDispatcherHost::RenderViewForRequest(
     94           request_, &render_process_host_id_,  &tab_contents_id_)) {
     95     NOTREACHED();
     96   }
     97 }
     98 
     99 LoginHandler::~LoginHandler() {
    100   SetModel(NULL);
    101 }
    102 
    103 void LoginHandler::SetPasswordForm(const webkit_glue::PasswordForm& form) {
    104   password_form_ = form;
    105 }
    106 
    107 void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
    108   password_manager_ = password_manager;
    109 }
    110 
    111 TabContents* LoginHandler::GetTabContentsForLogin() const {
    112   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    113 
    114   return tab_util::GetTabContentsByID(render_process_host_id_,
    115                                       tab_contents_id_);
    116 }
    117 
    118 RenderViewHostDelegate* LoginHandler::GetRenderViewHostDelegate() const {
    119   RenderViewHost* rvh = RenderViewHost::FromID(render_process_host_id_,
    120                                                tab_contents_id_);
    121   if (!rvh)
    122     return NULL;
    123 
    124   return rvh->delegate();
    125 }
    126 
    127 void LoginHandler::SetAuth(const string16& username,
    128                            const string16& password) {
    129   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    130 
    131   if (TestAndSetAuthHandled())
    132     return;
    133 
    134   // Tell the password manager the credentials were submitted / accepted.
    135   if (password_manager_) {
    136     password_form_.username_value = username;
    137     password_form_.password_value = password;
    138     password_manager_->ProvisionallySavePassword(password_form_);
    139   }
    140 
    141   // Calling NotifyAuthSupplied() directly instead of posting a task
    142   // allows other LoginHandler instances to queue their
    143   // CloseContentsDeferred() before ours.  Closing dialogs in the
    144   // opposite order as they were created avoids races where remaining
    145   // dialogs in the same tab may be briefly displayed to the user
    146   // before they are removed.
    147   NotifyAuthSupplied(username, password);
    148 
    149   BrowserThread::PostTask(
    150       BrowserThread::UI, FROM_HERE,
    151       NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred));
    152   BrowserThread::PostTask(
    153       BrowserThread::IO, FROM_HERE,
    154       NewRunnableMethod(
    155           this, &LoginHandler::SetAuthDeferred, username, password));
    156 }
    157 
    158 void LoginHandler::CancelAuth() {
    159   if (TestAndSetAuthHandled())
    160     return;
    161 
    162   // Similar to how we deal with notifications above in SetAuth()
    163   if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    164     NotifyAuthCancelled();
    165   } else {
    166     BrowserThread::PostTask(
    167         BrowserThread::UI, FROM_HERE,
    168         NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled));
    169   }
    170 
    171   BrowserThread::PostTask(
    172       BrowserThread::UI, FROM_HERE,
    173       NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred));
    174   BrowserThread::PostTask(
    175       BrowserThread::IO, FROM_HERE,
    176       NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred));
    177 }
    178 
    179 void LoginHandler::OnRequestCancelled() {
    180   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
    181       "Why is OnRequestCancelled called from the UI thread?";
    182 
    183   // Reference is no longer valid.
    184   request_ = NULL;
    185 
    186   // Give up on auth if the request was cancelled.
    187   CancelAuth();
    188 }
    189 
    190 void LoginHandler::AddObservers() {
    191   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    192 
    193   registrar_.Add(this, NotificationType::AUTH_SUPPLIED,
    194                  NotificationService::AllSources());
    195   registrar_.Add(this, NotificationType::AUTH_CANCELLED,
    196                  NotificationService::AllSources());
    197 }
    198 
    199 void LoginHandler::RemoveObservers() {
    200   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    201 
    202   registrar_.Remove(this, NotificationType::AUTH_SUPPLIED,
    203                     NotificationService::AllSources());
    204   registrar_.Remove(this, NotificationType::AUTH_CANCELLED,
    205                     NotificationService::AllSources());
    206 
    207   DCHECK(registrar_.IsEmpty());
    208 }
    209 
    210 void LoginHandler::Observe(NotificationType type,
    211                            const NotificationSource& source,
    212                            const NotificationDetails& details) {
    213   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    214   DCHECK(type == NotificationType::AUTH_SUPPLIED ||
    215          type == NotificationType::AUTH_CANCELLED);
    216 
    217   TabContents* requesting_contents = GetTabContentsForLogin();
    218   if (!requesting_contents)
    219     return;
    220 
    221   // Break out early if we aren't interested in the notification.
    222   if (WasAuthHandled())
    223     return;
    224 
    225   LoginNotificationDetails* login_details =
    226       Details<LoginNotificationDetails>(details).ptr();
    227 
    228   // WasAuthHandled() should always test positive before we publish
    229   // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
    230   DCHECK(login_details->handler() != this);
    231 
    232   // Only handle notification for the identical auth info.
    233   if (*login_details->handler()->auth_info() != *auth_info())
    234     return;
    235 
    236   // Set or cancel the auth in this handler.
    237   if (type == NotificationType::AUTH_SUPPLIED) {
    238     AuthSuppliedLoginNotificationDetails* supplied_details =
    239         Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
    240     SetAuth(supplied_details->username(), supplied_details->password());
    241   } else {
    242     DCHECK(type == NotificationType::AUTH_CANCELLED);
    243     CancelAuth();
    244   }
    245 }
    246 
    247 void LoginHandler::SetModel(LoginModel* model) {
    248   if (login_model_)
    249     login_model_->SetObserver(NULL);
    250   login_model_ = model;
    251   if (login_model_)
    252     login_model_->SetObserver(this);
    253 }
    254 
    255 void LoginHandler::SetDialog(ConstrainedWindow* dialog) {
    256   dialog_ = dialog;
    257 }
    258 
    259 void LoginHandler::NotifyAuthNeeded() {
    260   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    261   if (WasAuthHandled())
    262     return;
    263 
    264   NotificationService* service = NotificationService::current();
    265   NavigationController* controller = NULL;
    266 
    267   TabContents* requesting_contents = GetTabContentsForLogin();
    268   if (requesting_contents)
    269     controller = &requesting_contents->controller();
    270 
    271   LoginNotificationDetails details(this);
    272 
    273   service->Notify(NotificationType::AUTH_NEEDED,
    274                   Source<NavigationController>(controller),
    275                   Details<LoginNotificationDetails>(&details));
    276 }
    277 
    278 void LoginHandler::NotifyAuthCancelled() {
    279   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    280   DCHECK(WasAuthHandled());
    281 
    282   NotificationService* service = NotificationService::current();
    283   NavigationController* controller = NULL;
    284 
    285   TabContents* requesting_contents = GetTabContentsForLogin();
    286   if (requesting_contents)
    287     controller = &requesting_contents->controller();
    288 
    289   LoginNotificationDetails details(this);
    290 
    291   service->Notify(NotificationType::AUTH_CANCELLED,
    292                   Source<NavigationController>(controller),
    293                   Details<LoginNotificationDetails>(&details));
    294 }
    295 
    296 void LoginHandler::NotifyAuthSupplied(const string16& username,
    297                                       const string16& password) {
    298   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    299   DCHECK(WasAuthHandled());
    300 
    301   TabContents* requesting_contents = GetTabContentsForLogin();
    302   if (!requesting_contents)
    303     return;
    304 
    305   NotificationService* service = NotificationService::current();
    306   NavigationController* controller = &requesting_contents->controller();
    307   AuthSuppliedLoginNotificationDetails details(this, username, password);
    308 
    309   service->Notify(NotificationType::AUTH_SUPPLIED,
    310                   Source<NavigationController>(controller),
    311                   Details<AuthSuppliedLoginNotificationDetails>(&details));
    312 }
    313 
    314 void LoginHandler::ReleaseSoon() {
    315   if (!TestAndSetAuthHandled()) {
    316     BrowserThread::PostTask(
    317         BrowserThread::IO, FROM_HERE,
    318         NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred));
    319     BrowserThread::PostTask(
    320         BrowserThread::UI, FROM_HERE,
    321         NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled));
    322   }
    323 
    324   BrowserThread::PostTask(
    325     BrowserThread::UI, FROM_HERE,
    326     NewRunnableMethod(this, &LoginHandler::RemoveObservers));
    327 
    328   // Delete this object once all InvokeLaters have been called.
    329   BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
    330 }
    331 
    332 // Returns whether authentication had been handled (SetAuth or CancelAuth).
    333 bool LoginHandler::WasAuthHandled() const {
    334   base::AutoLock lock(handled_auth_lock_);
    335   bool was_handled = handled_auth_;
    336   return was_handled;
    337 }
    338 
    339 // Marks authentication as handled and returns the previous handled state.
    340 bool LoginHandler::TestAndSetAuthHandled() {
    341   base::AutoLock lock(handled_auth_lock_);
    342   bool was_handled = handled_auth_;
    343   handled_auth_ = true;
    344   return was_handled;
    345 }
    346 
    347 // Calls SetAuth from the IO loop.
    348 void LoginHandler::SetAuthDeferred(const string16& username,
    349                                    const string16& password) {
    350   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    351 
    352   if (request_) {
    353     request_->SetAuth(username, password);
    354     ResetLoginHandlerForRequest(request_);
    355   }
    356 }
    357 
    358 // Calls CancelAuth from the IO loop.
    359 void LoginHandler::CancelAuthDeferred() {
    360   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    361 
    362   if (request_) {
    363     request_->CancelAuth();
    364     // Verify that CancelAuth doesn't destroy the request via our delegate.
    365     DCHECK(request_ != NULL);
    366     ResetLoginHandlerForRequest(request_);
    367   }
    368 }
    369 
    370 // Closes the view_contents from the UI loop.
    371 void LoginHandler::CloseContentsDeferred() {
    372   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    373 
    374   // The hosting ConstrainedWindow may have been freed.
    375   if (dialog_)
    376     dialog_->CloseConstrainedWindow();
    377 }
    378 
    379 // ----------------------------------------------------------------------------
    380 // LoginDialogTask
    381 
    382 // This task is run on the UI thread and creates a constrained window with
    383 // a LoginView to prompt the user.  The response will be sent to LoginHandler,
    384 // which then routes it to the net::URLRequest on the I/O thread.
    385 class LoginDialogTask : public Task {
    386  public:
    387   LoginDialogTask(const GURL& request_url,
    388                   net::AuthChallengeInfo* auth_info,
    389                   LoginHandler* handler)
    390       : request_url_(request_url), auth_info_(auth_info), handler_(handler) {
    391   }
    392   virtual ~LoginDialogTask() {
    393   }
    394 
    395   void Run() {
    396     TabContents* parent_contents = handler_->GetTabContentsForLogin();
    397     if (!parent_contents || handler_->WasAuthHandled()) {
    398       // The request may have been cancelled, or it may be for a renderer
    399       // not hosted by a tab (e.g. an extension). Cancel just in case
    400       // (cancelling twice is a no-op).
    401       handler_->CancelAuth();
    402       return;
    403     }
    404 
    405     // Tell the password manager to look for saved passwords.
    406     TabContentsWrapper* wrapper =
    407         TabContentsWrapper::GetCurrentWrapperForContents(parent_contents);
    408     if (!wrapper)
    409       return;
    410     PasswordManager* password_manager = wrapper->password_manager();
    411     std::vector<PasswordForm> v;
    412     MakeInputForPasswordManager(&v);
    413     password_manager->OnPasswordFormsFound(v);
    414     handler_->SetPasswordManager(password_manager);
    415 
    416     string16 host_and_port_hack16 = WideToUTF16Hack(auth_info_->host_and_port);
    417     string16 realm_hack16 = WideToUTF16Hack(auth_info_->realm);
    418     string16 explanation = realm_hack16.empty() ?
    419         l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
    420                                    host_and_port_hack16) :
    421         l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
    422                                    host_and_port_hack16,
    423                                    realm_hack16);
    424     handler_->BuildViewForPasswordManager(password_manager, explanation);
    425   }
    426 
    427  private:
    428   // Helper to create a PasswordForm and stuff it into a vector as input
    429   // for PasswordManager::PasswordFormsFound, the hook into PasswordManager.
    430   void MakeInputForPasswordManager(
    431       std::vector<PasswordForm>* password_manager_input) {
    432     PasswordForm dialog_form;
    433     if (LowerCaseEqualsASCII(auth_info_->scheme, "basic")) {
    434       dialog_form.scheme = PasswordForm::SCHEME_BASIC;
    435     } else if (LowerCaseEqualsASCII(auth_info_->scheme, "digest")) {
    436       dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
    437     } else {
    438       dialog_form.scheme = PasswordForm::SCHEME_OTHER;
    439     }
    440     std::string host_and_port(WideToASCII(auth_info_->host_and_port));
    441     if (auth_info_->is_proxy) {
    442       std::string origin = host_and_port;
    443       // We don't expect this to already start with http:// or https://.
    444       DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
    445       origin = std::string("http://") + origin;
    446       dialog_form.origin = GURL(origin);
    447     } else if (net::GetHostAndPort(request_url_) != host_and_port) {
    448       dialog_form.origin = GURL();
    449       NOTREACHED();  // crbug.com/32718
    450     } else {
    451       dialog_form.origin = GURL(request_url_.scheme() + "://" + host_and_port);
    452     }
    453     dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info_);
    454     password_manager_input->push_back(dialog_form);
    455     // Set the password form for the handler (by copy).
    456     handler_->SetPasswordForm(dialog_form);
    457   }
    458 
    459   // The url from the net::URLRequest initiating the auth challenge.
    460   GURL request_url_;
    461 
    462   // Info about who/where/what is asking for authentication.
    463   scoped_refptr<net::AuthChallengeInfo> auth_info_;
    464 
    465   // Where to send the authentication when obtained.
    466   // This is owned by the ResourceDispatcherHost that invoked us.
    467   LoginHandler* handler_;
    468 
    469   DISALLOW_COPY_AND_ASSIGN(LoginDialogTask);
    470 };
    471 
    472 // ----------------------------------------------------------------------------
    473 // Public API
    474 
    475 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
    476                                 net::URLRequest* request) {
    477   LoginHandler* handler = LoginHandler::Create(auth_info, request);
    478   BrowserThread::PostTask(
    479       BrowserThread::UI, FROM_HERE, new LoginDialogTask(
    480           request->url(), auth_info, handler));
    481   return handler;
    482 }
    483