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/string_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "base/synchronization/lock.h"
     14 #include "chrome/browser/chrome_notification_types.h"
     15 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
     16 #include "chrome/browser/prerender/prerender_contents.h"
     17 #include "chrome/browser/tab_contents/tab_util.h"
     18 #include "chrome/browser/ui/login/login_interstitial_delegate.h"
     19 #include "chrome/grit/generated_resources.h"
     20 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
     21 #include "components/password_manager/core/browser/password_manager.h"
     22 #include "content/public/browser/browser_thread.h"
     23 #include "content/public/browser/notification_registrar.h"
     24 #include "content/public/browser/notification_service.h"
     25 #include "content/public/browser/render_frame_host.h"
     26 #include "content/public/browser/resource_dispatcher_host.h"
     27 #include "content/public/browser/resource_request_info.h"
     28 #include "content/public/browser/web_contents.h"
     29 #include "net/base/auth.h"
     30 #include "net/base/load_flags.h"
     31 #include "net/base/net_util.h"
     32 #include "net/http/http_transaction_factory.h"
     33 #include "net/url_request/url_request.h"
     34 #include "net/url_request/url_request_context.h"
     35 #include "ui/base/l10n/l10n_util.h"
     36 #include "ui/gfx/text_elider.h"
     37 
     38 using autofill::PasswordForm;
     39 using content::BrowserThread;
     40 using content::NavigationController;
     41 using content::RenderViewHost;
     42 using content::RenderViewHostDelegate;
     43 using content::ResourceDispatcherHost;
     44 using content::ResourceRequestInfo;
     45 using content::WebContents;
     46 
     47 class LoginHandlerImpl;
     48 
     49 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
     50 // Should only be called from the IO thread, since it accesses an
     51 // net::URLRequest.
     52 void ResetLoginHandlerForRequest(net::URLRequest* request) {
     53   ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
     54 }
     55 
     56 // Get the signon_realm under which this auth info should be stored.
     57 //
     58 // The format of the signon_realm for proxy auth is:
     59 //     proxy-host/auth-realm
     60 // The format of the signon_realm for server auth is:
     61 //     url-scheme://url-host[:url-port]/auth-realm
     62 //
     63 // Be careful when changing this function, since you could make existing
     64 // saved logins un-retrievable.
     65 std::string GetSignonRealm(const GURL& url,
     66                            const net::AuthChallengeInfo& auth_info) {
     67   std::string signon_realm;
     68   if (auth_info.is_proxy) {
     69     signon_realm = auth_info.challenger.ToString();
     70     signon_realm.append("/");
     71   } else {
     72     // Take scheme, host, and port from the url.
     73     signon_realm = url.GetOrigin().spec();
     74     // This ends with a "/".
     75   }
     76   signon_realm.append(auth_info.realm);
     77   return signon_realm;
     78 }
     79 
     80 // ----------------------------------------------------------------------------
     81 // LoginHandler
     82 
     83 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
     84                            net::URLRequest* request)
     85     : handled_auth_(false),
     86       auth_info_(auth_info),
     87       request_(request),
     88       http_network_session_(
     89           request_->context()->http_transaction_factory()->GetSession()),
     90       password_manager_(NULL),
     91       login_model_(NULL) {
     92   // This constructor is called on the I/O thread, so we cannot load the nib
     93   // here. BuildViewForPasswordManager() will be invoked on the UI thread
     94   // later, so wait with loading the nib until then.
     95   DCHECK(request_) << "LoginHandler constructed with NULL request";
     96   DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
     97 
     98   AddRef();  // matched by LoginHandler::ReleaseSoon().
     99 
    100   BrowserThread::PostTask(
    101       BrowserThread::UI, FROM_HERE,
    102       base::Bind(&LoginHandler::AddObservers, this));
    103 
    104   if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
    105           &render_process_host_id_,  &render_frame_id_)) {
    106     NOTREACHED();
    107   }
    108 }
    109 
    110 void LoginHandler::OnRequestCancelled() {
    111   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
    112       "Why is OnRequestCancelled called from the UI thread?";
    113 
    114   // Reference is no longer valid.
    115   request_ = NULL;
    116 
    117   // Give up on auth if the request was cancelled.
    118   CancelAuth();
    119 }
    120 
    121 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
    122   password_form_ = form;
    123 }
    124 
    125 void LoginHandler::SetPasswordManager(
    126     password_manager::PasswordManager* password_manager) {
    127   password_manager_ = password_manager;
    128 }
    129 
    130 WebContents* LoginHandler::GetWebContentsForLogin() const {
    131   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    132 
    133   content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
    134       render_process_host_id_, render_frame_id_);
    135   return WebContents::FromRenderFrameHost(rfh);
    136 }
    137 
    138 void LoginHandler::SetAuth(const base::string16& username,
    139                            const base::string16& password) {
    140   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    141 
    142   scoped_ptr<password_manager::BrowserSavePasswordProgressLogger> logger;
    143   if (password_manager_ && password_manager_->client()->IsLoggingActive()) {
    144     logger.reset(new password_manager::BrowserSavePasswordProgressLogger(
    145         password_manager_->client()));
    146     logger->LogMessage(
    147         autofill::SavePasswordProgressLogger::STRING_SET_AUTH_METHOD);
    148   }
    149 
    150   bool already_handled = TestAndSetAuthHandled();
    151   if (logger) {
    152     logger->LogBoolean(
    153         autofill::SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED,
    154         already_handled);
    155   }
    156   if (already_handled)
    157     return;
    158 
    159   // Tell the password manager the credentials were submitted / accepted.
    160   if (password_manager_) {
    161     password_form_.username_value = username;
    162     password_form_.password_value = password;
    163     password_manager_->ProvisionallySavePassword(password_form_);
    164     if (logger) {
    165       logger->LogPasswordForm(
    166           autofill::SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM,
    167           password_form_);
    168     }
    169   }
    170 
    171   // Calling NotifyAuthSupplied() directly instead of posting a task
    172   // allows other LoginHandler instances to queue their
    173   // CloseContentsDeferred() before ours.  Closing dialogs in the
    174   // opposite order as they were created avoids races where remaining
    175   // dialogs in the same tab may be briefly displayed to the user
    176   // before they are removed.
    177   NotifyAuthSupplied(username, password);
    178 
    179   BrowserThread::PostTask(
    180       BrowserThread::UI, FROM_HERE,
    181       base::Bind(&LoginHandler::CloseContentsDeferred, this));
    182   BrowserThread::PostTask(
    183       BrowserThread::IO, FROM_HERE,
    184       base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
    185 }
    186 
    187 void LoginHandler::CancelAuth() {
    188   if (TestAndSetAuthHandled())
    189     return;
    190 
    191   // Similar to how we deal with notifications above in SetAuth()
    192   if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    193     NotifyAuthCancelled();
    194   } else {
    195     BrowserThread::PostTask(
    196         BrowserThread::UI, FROM_HERE,
    197         base::Bind(&LoginHandler::NotifyAuthCancelled, this));
    198   }
    199 
    200   BrowserThread::PostTask(
    201       BrowserThread::UI, FROM_HERE,
    202       base::Bind(&LoginHandler::CloseContentsDeferred, this));
    203   BrowserThread::PostTask(
    204       BrowserThread::IO, FROM_HERE,
    205       base::Bind(&LoginHandler::CancelAuthDeferred, this));
    206 }
    207 
    208 
    209 void LoginHandler::Observe(int type,
    210                            const content::NotificationSource& source,
    211                            const content::NotificationDetails& details) {
    212   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    213   DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
    214          type == chrome::NOTIFICATION_AUTH_CANCELLED);
    215 
    216   WebContents* requesting_contents = GetWebContentsForLogin();
    217   if (!requesting_contents)
    218     return;
    219 
    220   // Break out early if we aren't interested in the notification.
    221   if (WasAuthHandled())
    222     return;
    223 
    224   LoginNotificationDetails* login_details =
    225       content::Details<LoginNotificationDetails>(details).ptr();
    226 
    227   // WasAuthHandled() should always test positive before we publish
    228   // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
    229   DCHECK(login_details->handler() != this);
    230 
    231   // Only handle notification for the identical auth info.
    232   if (!login_details->handler()->auth_info()->Equals(*auth_info()))
    233     return;
    234 
    235   // Ignore login notification events from other profiles.
    236   if (login_details->handler()->http_network_session_ !=
    237       http_network_session_)
    238     return;
    239 
    240   // Set or cancel the auth in this handler.
    241   if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
    242     AuthSuppliedLoginNotificationDetails* supplied_details =
    243         content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
    244     SetAuth(supplied_details->username(), supplied_details->password());
    245   } else {
    246     DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
    247     CancelAuth();
    248   }
    249 }
    250 
    251 // Returns whether authentication had been handled (SetAuth or CancelAuth).
    252 bool LoginHandler::WasAuthHandled() const {
    253   base::AutoLock lock(handled_auth_lock_);
    254   bool was_handled = handled_auth_;
    255   return was_handled;
    256 }
    257 
    258 LoginHandler::~LoginHandler() {
    259   SetModel(NULL);
    260 }
    261 
    262 void LoginHandler::SetModel(password_manager::LoginModel* model) {
    263   if (login_model_)
    264     login_model_->RemoveObserver(this);
    265   login_model_ = model;
    266   if (login_model_)
    267     login_model_->AddObserver(this);
    268 }
    269 
    270 void LoginHandler::NotifyAuthNeeded() {
    271   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    272   if (WasAuthHandled())
    273     return;
    274 
    275   content::NotificationService* service =
    276       content::NotificationService::current();
    277   NavigationController* controller = NULL;
    278 
    279   WebContents* requesting_contents = GetWebContentsForLogin();
    280   if (requesting_contents)
    281     controller = &requesting_contents->GetController();
    282 
    283   LoginNotificationDetails details(this);
    284 
    285   service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
    286                   content::Source<NavigationController>(controller),
    287                   content::Details<LoginNotificationDetails>(&details));
    288 }
    289 
    290 void LoginHandler::ReleaseSoon() {
    291   if (!TestAndSetAuthHandled()) {
    292     BrowserThread::PostTask(
    293         BrowserThread::IO, FROM_HERE,
    294         base::Bind(&LoginHandler::CancelAuthDeferred, this));
    295     BrowserThread::PostTask(
    296         BrowserThread::UI, FROM_HERE,
    297         base::Bind(&LoginHandler::NotifyAuthCancelled, this));
    298   }
    299 
    300   BrowserThread::PostTask(
    301     BrowserThread::UI, FROM_HERE,
    302     base::Bind(&LoginHandler::RemoveObservers, this));
    303 
    304   // Delete this object once all InvokeLaters have been called.
    305   BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
    306 }
    307 
    308 void LoginHandler::AddObservers() {
    309   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    310 
    311   // This is probably OK; we need to listen to everything and we break out of
    312   // the Observe() if we aren't handling the same auth_info().
    313   registrar_.reset(new content::NotificationRegistrar);
    314   registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
    315                   content::NotificationService::AllBrowserContextsAndSources());
    316   registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
    317                   content::NotificationService::AllBrowserContextsAndSources());
    318 }
    319 
    320 void LoginHandler::RemoveObservers() {
    321   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    322 
    323   registrar_.reset();
    324 }
    325 
    326 void LoginHandler::NotifyAuthSupplied(const base::string16& username,
    327                                       const base::string16& password) {
    328   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    329   DCHECK(WasAuthHandled());
    330 
    331   WebContents* requesting_contents = GetWebContentsForLogin();
    332   if (!requesting_contents)
    333     return;
    334 
    335   content::NotificationService* service =
    336       content::NotificationService::current();
    337   NavigationController* controller =
    338       &requesting_contents->GetController();
    339   AuthSuppliedLoginNotificationDetails details(this, username, password);
    340 
    341   service->Notify(
    342       chrome::NOTIFICATION_AUTH_SUPPLIED,
    343       content::Source<NavigationController>(controller),
    344       content::Details<AuthSuppliedLoginNotificationDetails>(&details));
    345 }
    346 
    347 void LoginHandler::NotifyAuthCancelled() {
    348   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    349   DCHECK(WasAuthHandled());
    350 
    351   content::NotificationService* service =
    352       content::NotificationService::current();
    353   NavigationController* controller = NULL;
    354 
    355   WebContents* requesting_contents = GetWebContentsForLogin();
    356   if (requesting_contents)
    357     controller = &requesting_contents->GetController();
    358 
    359   LoginNotificationDetails details(this);
    360 
    361   service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
    362                   content::Source<NavigationController>(controller),
    363                   content::Details<LoginNotificationDetails>(&details));
    364 }
    365 
    366 // Marks authentication as handled and returns the previous handled state.
    367 bool LoginHandler::TestAndSetAuthHandled() {
    368   base::AutoLock lock(handled_auth_lock_);
    369   bool was_handled = handled_auth_;
    370   handled_auth_ = true;
    371   return was_handled;
    372 }
    373 
    374 // Calls SetAuth from the IO loop.
    375 void LoginHandler::SetAuthDeferred(const base::string16& username,
    376                                    const base::string16& password) {
    377   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    378 
    379   if (request_) {
    380     request_->SetAuth(net::AuthCredentials(username, password));
    381     ResetLoginHandlerForRequest(request_);
    382   }
    383 }
    384 
    385 // Calls CancelAuth from the IO loop.
    386 void LoginHandler::CancelAuthDeferred() {
    387   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    388 
    389   if (request_) {
    390     request_->CancelAuth();
    391     // Verify that CancelAuth doesn't destroy the request via our delegate.
    392     DCHECK(request_ != NULL);
    393     ResetLoginHandlerForRequest(request_);
    394   }
    395 }
    396 
    397 // Closes the view_contents from the UI loop.
    398 void LoginHandler::CloseContentsDeferred() {
    399   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    400 
    401   CloseDialog();
    402 
    403   WebContents* requesting_contents = GetWebContentsForLogin();
    404   if (!requesting_contents)
    405     return;
    406   // If a (blank) login interstitial was displayed, proceed so that the
    407   // navigation is committed.
    408   content::InterstitialPage* interstitial_page =
    409       requesting_contents->GetInterstitialPage();
    410   if (interstitial_page)
    411     interstitial_page->Proceed();
    412 }
    413 
    414 // Helper to create a PasswordForm and stuff it into a vector as input
    415 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
    416 void MakeInputForPasswordManager(
    417     const GURL& request_url,
    418     net::AuthChallengeInfo* auth_info,
    419     LoginHandler* handler,
    420     std::vector<PasswordForm>* password_manager_input) {
    421   PasswordForm dialog_form;
    422   if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
    423     dialog_form.scheme = PasswordForm::SCHEME_BASIC;
    424   } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
    425     dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
    426   } else {
    427     dialog_form.scheme = PasswordForm::SCHEME_OTHER;
    428   }
    429   std::string host_and_port(auth_info->challenger.ToString());
    430   if (auth_info->is_proxy) {
    431     std::string origin = host_and_port;
    432     // We don't expect this to already start with http:// or https://.
    433     DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
    434     origin = std::string("http://") + origin;
    435     dialog_form.origin = GURL(origin);
    436   } else if (!auth_info->challenger.Equals(
    437       net::HostPortPair::FromURL(request_url))) {
    438     dialog_form.origin = GURL();
    439     NOTREACHED();  // crbug.com/32718
    440   } else {
    441     dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
    442   }
    443   dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
    444   password_manager_input->push_back(dialog_form);
    445   // Set the password form for the handler (by copy).
    446   handler->SetPasswordForm(dialog_form);
    447 }
    448 
    449 void ShowLoginPrompt(const GURL& request_url,
    450                      net::AuthChallengeInfo* auth_info,
    451                      LoginHandler* handler) {
    452   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    453   WebContents* parent_contents = handler->GetWebContentsForLogin();
    454   if (!parent_contents)
    455     return;
    456   prerender::PrerenderContents* prerender_contents =
    457       prerender::PrerenderContents::FromWebContents(parent_contents);
    458   if (prerender_contents) {
    459     prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
    460     return;
    461   }
    462 
    463   password_manager::PasswordManager* password_manager =
    464       ChromePasswordManagerClient::GetManagerFromWebContents(parent_contents);
    465   if (!password_manager) {
    466     // Same logic as above.
    467     handler->CancelAuth();
    468     return;
    469   }
    470 
    471   // Tell the password manager to look for saved passwords.
    472   std::vector<PasswordForm> v;
    473   MakeInputForPasswordManager(request_url, auth_info, handler, &v);
    474   password_manager->OnPasswordFormsParsed(v);
    475   handler->SetPasswordManager(password_manager);
    476 
    477   // The realm is controlled by the remote server, so there is no reason
    478   // to believe it is of a reasonable length.
    479   base::string16 elided_realm;
    480   gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
    481 
    482   base::string16 host_and_port = base::ASCIIToUTF16(
    483       request_url.scheme() + "://" + auth_info->challenger.ToString());
    484   base::string16 explanation = elided_realm.empty() ?
    485       l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
    486                                  host_and_port) :
    487       l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
    488                                  host_and_port,
    489                                  elided_realm);
    490   handler->BuildViewForPasswordManager(password_manager, explanation);
    491 }
    492 
    493 // This callback is run on the UI thread and creates a constrained window with
    494 // a LoginView to prompt the user. If the prompt is triggered because of
    495 // a cross origin navigation in the main frame, a blank interstitial is first
    496 // created which in turn creates the LoginView. Otherwise, a LoginView is
    497 // directly in this callback. In both cases, the response will be sent to
    498 // LoginHandler, which then routes it to the net::URLRequest on the I/O thread.
    499 void LoginDialogCallback(const GURL& request_url,
    500                          net::AuthChallengeInfo* auth_info,
    501                          LoginHandler* handler,
    502                          bool is_main_frame) {
    503   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    504   WebContents* parent_contents = handler->GetWebContentsForLogin();
    505   if (!parent_contents || handler->WasAuthHandled()) {
    506     // The request may have been cancelled, or it may be for a renderer
    507     // not hosted by a tab (e.g. an extension). Cancel just in case
    508     // (cancelling twice is a no-op).
    509     handler->CancelAuth();
    510     return;
    511   }
    512 
    513   // Check if the request is cross origin. There are two different ways the
    514   // navigation can occur:
    515   // 1- The user enters the resource URL in the omnibox.
    516   // 2- The page redirects to the resource.
    517   // In both cases, the last committed URL is different than the resource URL,
    518   // so checking it is sufficient.
    519   // Note that (1) will not be true once site isolation is enabled, as any
    520   // navigation could cause a cross-process swap, including link clicks.
    521   if (is_main_frame &&
    522       parent_contents->GetLastCommittedURL().GetOrigin() !=
    523           request_url.GetOrigin()) {
    524     // Show a blank interstitial for main-frame, cross origin requests
    525     // so that the correct URL is shown in the omnibox.
    526     base::Closure callback = base::Bind(&ShowLoginPrompt,
    527                                         request_url,
    528                                         make_scoped_refptr(auth_info),
    529                                         make_scoped_refptr(handler));
    530     // This is owned by the interstitial it creates.
    531     new LoginInterstitialDelegate(parent_contents,
    532                                   request_url,
    533                                   callback);
    534   } else {
    535     ShowLoginPrompt(request_url,
    536                     auth_info,
    537                     handler);
    538   }
    539 }
    540 
    541 // ----------------------------------------------------------------------------
    542 // Public API
    543 
    544 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
    545                                 net::URLRequest* request) {
    546   bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0;
    547   LoginHandler* handler = LoginHandler::Create(auth_info, request);
    548   BrowserThread::PostTask(
    549       BrowserThread::UI, FROM_HERE,
    550       base::Bind(&LoginDialogCallback, request->url(),
    551                  make_scoped_refptr(auth_info), make_scoped_refptr(handler),
    552                  is_main_frame));
    553   return handler;
    554 }
    555