Home | History | Annotate | Download | only in login
      1 // Copyright (c) 2012 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/bind.h"
     10 #include "base/command_line.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "base/synchronization/lock.h"
     13 #include "chrome/browser/chrome_notification_types.h"
     14 #include "chrome/browser/password_manager/password_manager.h"
     15 #include "chrome/browser/tab_contents/tab_util.h"
     16 #include "content/public/browser/browser_thread.h"
     17 #include "content/public/browser/notification_registrar.h"
     18 #include "content/public/browser/notification_service.h"
     19 #include "content/public/browser/render_view_host.h"
     20 #include "content/public/browser/resource_dispatcher_host.h"
     21 #include "content/public/browser/resource_request_info.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "grit/generated_resources.h"
     24 #include "net/base/auth.h"
     25 #include "net/base/net_util.h"
     26 #include "net/http/http_transaction_factory.h"
     27 #include "net/url_request/url_request.h"
     28 #include "net/url_request/url_request_context.h"
     29 #include "ui/base/l10n/l10n_util.h"
     30 #include "ui/base/text/text_elider.h"
     31 
     32 using content::BrowserThread;
     33 using content::NavigationController;
     34 using content::RenderViewHost;
     35 using content::RenderViewHostDelegate;
     36 using content::ResourceDispatcherHost;
     37 using content::ResourceRequestInfo;
     38 using content::WebContents;
     39 using content::PasswordForm;
     40 
     41 class LoginHandlerImpl;
     42 
     43 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
     44 // Should only be called from the IO thread, since it accesses an
     45 // net::URLRequest.
     46 void ResetLoginHandlerForRequest(net::URLRequest* request) {
     47   ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
     48 }
     49 
     50 // Get the signon_realm under which this auth info should be stored.
     51 //
     52 // The format of the signon_realm for proxy auth is:
     53 //     proxy-host/auth-realm
     54 // The format of the signon_realm for server auth is:
     55 //     url-scheme://url-host[:url-port]/auth-realm
     56 //
     57 // Be careful when changing this function, since you could make existing
     58 // saved logins un-retrievable.
     59 std::string GetSignonRealm(const GURL& url,
     60                            const net::AuthChallengeInfo& auth_info) {
     61   std::string signon_realm;
     62   if (auth_info.is_proxy) {
     63     signon_realm = auth_info.challenger.ToString();
     64     signon_realm.append("/");
     65   } else {
     66     // Take scheme, host, and port from the url.
     67     signon_realm = url.GetOrigin().spec();
     68     // This ends with a "/".
     69   }
     70   signon_realm.append(auth_info.realm);
     71   return signon_realm;
     72 }
     73 
     74 // ----------------------------------------------------------------------------
     75 // LoginHandler
     76 
     77 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
     78                            net::URLRequest* request)
     79     : handled_auth_(false),
     80       auth_info_(auth_info),
     81       request_(request),
     82       http_network_session_(
     83           request_->context()->http_transaction_factory()->GetSession()),
     84       password_manager_(NULL),
     85       login_model_(NULL) {
     86   // This constructor is called on the I/O thread, so we cannot load the nib
     87   // here. BuildViewForPasswordManager() will be invoked on the UI thread
     88   // later, so wait with loading the nib until then.
     89   DCHECK(request_) << "LoginHandler constructed with NULL request";
     90   DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
     91 
     92   AddRef();  // matched by LoginHandler::ReleaseSoon().
     93 
     94   BrowserThread::PostTask(
     95       BrowserThread::UI, FROM_HERE,
     96       base::Bind(&LoginHandler::AddObservers, this));
     97 
     98   if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderView(
     99           &render_process_host_id_,  &tab_contents_id_)) {
    100     NOTREACHED();
    101   }
    102 }
    103 
    104 void LoginHandler::OnRequestCancelled() {
    105   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
    106       "Why is OnRequestCancelled called from the UI thread?";
    107 
    108   // Reference is no longer valid.
    109   request_ = NULL;
    110 
    111   // Give up on auth if the request was cancelled.
    112   CancelAuth();
    113 }
    114 
    115 void LoginHandler::SetPasswordForm(const content::PasswordForm& form) {
    116   password_form_ = form;
    117 }
    118 
    119 void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
    120   password_manager_ = password_manager;
    121 }
    122 
    123 WebContents* LoginHandler::GetWebContentsForLogin() const {
    124   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    125 
    126   return tab_util::GetWebContentsByID(render_process_host_id_,
    127                                       tab_contents_id_);
    128 }
    129 
    130 void LoginHandler::SetAuth(const string16& username,
    131                            const string16& password) {
    132   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    133 
    134   if (TestAndSetAuthHandled())
    135     return;
    136 
    137   // Tell the password manager the credentials were submitted / accepted.
    138   if (password_manager_) {
    139     password_form_.username_value = username;
    140     password_form_.password_value = password;
    141     password_manager_->ProvisionallySavePassword(password_form_);
    142   }
    143 
    144   // Calling NotifyAuthSupplied() directly instead of posting a task
    145   // allows other LoginHandler instances to queue their
    146   // CloseContentsDeferred() before ours.  Closing dialogs in the
    147   // opposite order as they were created avoids races where remaining
    148   // dialogs in the same tab may be briefly displayed to the user
    149   // before they are removed.
    150   NotifyAuthSupplied(username, password);
    151 
    152   BrowserThread::PostTask(
    153       BrowserThread::UI, FROM_HERE,
    154       base::Bind(&LoginHandler::CloseContentsDeferred, this));
    155   BrowserThread::PostTask(
    156       BrowserThread::IO, FROM_HERE,
    157       base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
    158 }
    159 
    160 void LoginHandler::CancelAuth() {
    161   if (TestAndSetAuthHandled())
    162     return;
    163 
    164   // Similar to how we deal with notifications above in SetAuth()
    165   if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    166     NotifyAuthCancelled();
    167   } else {
    168     BrowserThread::PostTask(
    169         BrowserThread::UI, FROM_HERE,
    170         base::Bind(&LoginHandler::NotifyAuthCancelled, this));
    171   }
    172 
    173   BrowserThread::PostTask(
    174       BrowserThread::UI, FROM_HERE,
    175       base::Bind(&LoginHandler::CloseContentsDeferred, this));
    176   BrowserThread::PostTask(
    177       BrowserThread::IO, FROM_HERE,
    178       base::Bind(&LoginHandler::CancelAuthDeferred, this));
    179 }
    180 
    181 
    182 void LoginHandler::Observe(int type,
    183                            const content::NotificationSource& source,
    184                            const content::NotificationDetails& details) {
    185   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    186   DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
    187          type == chrome::NOTIFICATION_AUTH_CANCELLED);
    188 
    189   WebContents* requesting_contents = GetWebContentsForLogin();
    190   if (!requesting_contents)
    191     return;
    192 
    193   // Break out early if we aren't interested in the notification.
    194   if (WasAuthHandled())
    195     return;
    196 
    197   LoginNotificationDetails* login_details =
    198       content::Details<LoginNotificationDetails>(details).ptr();
    199 
    200   // WasAuthHandled() should always test positive before we publish
    201   // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
    202   DCHECK(login_details->handler() != this);
    203 
    204   // Only handle notification for the identical auth info.
    205   if (!login_details->handler()->auth_info()->Equals(*auth_info()))
    206     return;
    207 
    208   // Ignore login notification events from other profiles.
    209   if (login_details->handler()->http_network_session_ !=
    210       http_network_session_)
    211     return;
    212 
    213   // Set or cancel the auth in this handler.
    214   if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
    215     AuthSuppliedLoginNotificationDetails* supplied_details =
    216         content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
    217     SetAuth(supplied_details->username(), supplied_details->password());
    218   } else {
    219     DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
    220     CancelAuth();
    221   }
    222 }
    223 
    224 // Returns whether authentication had been handled (SetAuth or CancelAuth).
    225 bool LoginHandler::WasAuthHandled() const {
    226   base::AutoLock lock(handled_auth_lock_);
    227   bool was_handled = handled_auth_;
    228   return was_handled;
    229 }
    230 
    231 LoginHandler::~LoginHandler() {
    232   SetModel(NULL);
    233 }
    234 
    235 void LoginHandler::SetModel(LoginModel* model) {
    236   if (login_model_)
    237     login_model_->RemoveObserver(this);
    238   login_model_ = model;
    239   if (login_model_)
    240     login_model_->AddObserver(this);
    241 }
    242 
    243 void LoginHandler::NotifyAuthNeeded() {
    244   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    245   if (WasAuthHandled())
    246     return;
    247 
    248   content::NotificationService* service =
    249       content::NotificationService::current();
    250   NavigationController* controller = NULL;
    251 
    252   WebContents* requesting_contents = GetWebContentsForLogin();
    253   if (requesting_contents)
    254     controller = &requesting_contents->GetController();
    255 
    256   LoginNotificationDetails details(this);
    257 
    258   service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
    259                   content::Source<NavigationController>(controller),
    260                   content::Details<LoginNotificationDetails>(&details));
    261 }
    262 
    263 void LoginHandler::ReleaseSoon() {
    264   if (!TestAndSetAuthHandled()) {
    265     BrowserThread::PostTask(
    266         BrowserThread::IO, FROM_HERE,
    267         base::Bind(&LoginHandler::CancelAuthDeferred, this));
    268     BrowserThread::PostTask(
    269         BrowserThread::UI, FROM_HERE,
    270         base::Bind(&LoginHandler::NotifyAuthCancelled, this));
    271   }
    272 
    273   BrowserThread::PostTask(
    274     BrowserThread::UI, FROM_HERE,
    275     base::Bind(&LoginHandler::RemoveObservers, this));
    276 
    277   // Delete this object once all InvokeLaters have been called.
    278   BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
    279 }
    280 
    281 void LoginHandler::AddObservers() {
    282   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    283 
    284   // This is probably OK; we need to listen to everything and we break out of
    285   // the Observe() if we aren't handling the same auth_info().
    286   registrar_.reset(new content::NotificationRegistrar);
    287   registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
    288                   content::NotificationService::AllBrowserContextsAndSources());
    289   registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
    290                   content::NotificationService::AllBrowserContextsAndSources());
    291 }
    292 
    293 void LoginHandler::RemoveObservers() {
    294   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    295 
    296   registrar_.reset();
    297 }
    298 
    299 void LoginHandler::NotifyAuthSupplied(const string16& username,
    300                                       const string16& password) {
    301   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    302   DCHECK(WasAuthHandled());
    303 
    304   WebContents* requesting_contents = GetWebContentsForLogin();
    305   if (!requesting_contents)
    306     return;
    307 
    308   content::NotificationService* service =
    309       content::NotificationService::current();
    310   NavigationController* controller =
    311       &requesting_contents->GetController();
    312   AuthSuppliedLoginNotificationDetails details(this, username, password);
    313 
    314   service->Notify(
    315       chrome::NOTIFICATION_AUTH_SUPPLIED,
    316       content::Source<NavigationController>(controller),
    317       content::Details<AuthSuppliedLoginNotificationDetails>(&details));
    318 }
    319 
    320 void LoginHandler::NotifyAuthCancelled() {
    321   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    322   DCHECK(WasAuthHandled());
    323 
    324   content::NotificationService* service =
    325       content::NotificationService::current();
    326   NavigationController* controller = NULL;
    327 
    328   WebContents* requesting_contents = GetWebContentsForLogin();
    329   if (requesting_contents)
    330     controller = &requesting_contents->GetController();
    331 
    332   LoginNotificationDetails details(this);
    333 
    334   service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
    335                   content::Source<NavigationController>(controller),
    336                   content::Details<LoginNotificationDetails>(&details));
    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(net::AuthCredentials(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   CloseDialog();
    375 }
    376 
    377 // Helper to create a PasswordForm and stuff it into a vector as input
    378 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
    379 void MakeInputForPasswordManager(
    380     const GURL& request_url,
    381     net::AuthChallengeInfo* auth_info,
    382     LoginHandler* handler,
    383     std::vector<PasswordForm>* password_manager_input) {
    384   PasswordForm dialog_form;
    385   if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
    386     dialog_form.scheme = PasswordForm::SCHEME_BASIC;
    387   } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
    388     dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
    389   } else {
    390     dialog_form.scheme = PasswordForm::SCHEME_OTHER;
    391   }
    392   std::string host_and_port(auth_info->challenger.ToString());
    393   if (auth_info->is_proxy) {
    394     std::string origin = host_and_port;
    395     // We don't expect this to already start with http:// or https://.
    396     DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
    397     origin = std::string("http://") + origin;
    398     dialog_form.origin = GURL(origin);
    399   } else if (!auth_info->challenger.Equals(
    400       net::HostPortPair::FromURL(request_url))) {
    401     dialog_form.origin = GURL();
    402     NOTREACHED();  // crbug.com/32718
    403   } else {
    404     dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
    405   }
    406   dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
    407   password_manager_input->push_back(dialog_form);
    408   // Set the password form for the handler (by copy).
    409   handler->SetPasswordForm(dialog_form);
    410 }
    411 
    412 // This callback is run on the UI thread and creates a constrained window with
    413 // a LoginView to prompt the user.  The response will be sent to LoginHandler,
    414 // which then routes it to the net::URLRequest on the I/O thread.
    415 void LoginDialogCallback(const GURL& request_url,
    416                          net::AuthChallengeInfo* auth_info,
    417                          LoginHandler* handler) {
    418   WebContents* parent_contents = handler->GetWebContentsForLogin();
    419   if (!parent_contents || handler->WasAuthHandled()) {
    420     // The request may have been cancelled, or it may be for a renderer
    421     // not hosted by a tab (e.g. an extension). Cancel just in case
    422     // (cancelling twice is a no-op).
    423     handler->CancelAuth();
    424     return;
    425   }
    426 
    427   PasswordManager* password_manager =
    428       PasswordManager::FromWebContents(parent_contents);
    429   if (!password_manager) {
    430     // Same logic as above.
    431     handler->CancelAuth();
    432     return;
    433   }
    434 
    435   // Tell the password manager to look for saved passwords.
    436   std::vector<PasswordForm> v;
    437   MakeInputForPasswordManager(request_url, auth_info, handler, &v);
    438   password_manager->OnPasswordFormsParsed(v);
    439   handler->SetPasswordManager(password_manager);
    440 
    441   // The realm is controlled by the remote server, so there is no reason
    442   // to believe it is of a reasonable length.
    443   string16 elided_realm;
    444   ui::ElideString(UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
    445 
    446   string16 host_and_port = ASCIIToUTF16(request_url.scheme() + "://" +
    447                                         auth_info->challenger.ToString());
    448   string16 explanation = elided_realm.empty() ?
    449       l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
    450                                  host_and_port) :
    451       l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
    452                                  host_and_port,
    453                                  elided_realm);
    454   handler->BuildViewForPasswordManager(password_manager, explanation);
    455 }
    456 
    457 // ----------------------------------------------------------------------------
    458 // Public API
    459 
    460 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
    461                                 net::URLRequest* request) {
    462   LoginHandler* handler = LoginHandler::Create(auth_info, request);
    463   BrowserThread::PostTask(
    464       BrowserThread::UI, FROM_HERE,
    465       base::Bind(&LoginDialogCallback, request->url(),
    466                  make_scoped_refptr(auth_info), make_scoped_refptr(handler)));
    467   return handler;
    468 }
    469