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