root/chrome/browser/ui/login/login_prompt.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ResetLoginHandlerForRequest
  2. GetSignonRealm
  3. login_model_
  4. OnRequestCancelled
  5. SetPasswordForm
  6. SetPasswordManager
  7. GetWebContentsForLogin
  8. SetAuth
  9. CancelAuth
  10. Observe
  11. WasAuthHandled
  12. SetModel
  13. NotifyAuthNeeded
  14. ReleaseSoon
  15. AddObservers
  16. RemoveObservers
  17. NotifyAuthSupplied
  18. NotifyAuthCancelled
  19. TestAndSetAuthHandled
  20. SetAuthDeferred
  21. CancelAuthDeferred
  22. CloseContentsDeferred
  23. MakeInputForPasswordManager
  24. LoginDialogCallback
  25. CreateLoginPrompt

// Copyright (c) 2012 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/ui/login/login_prompt.h"

#include <vector>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/password_manager/chrome_password_manager_client.h"
#include "chrome/browser/prerender/prerender_contents.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "components/password_manager/core/browser/password_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/resource_dispatcher_host.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "net/base/auth.h"
#include "net/base/net_util.h"
#include "net/http/http_transaction_factory.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/text_elider.h"

using autofill::PasswordForm;
using content::BrowserThread;
using content::NavigationController;
using content::RenderViewHost;
using content::RenderViewHostDelegate;
using content::ResourceDispatcherHost;
using content::ResourceRequestInfo;
using content::WebContents;

class LoginHandlerImpl;

// Helper to remove the ref from an net::URLRequest to the LoginHandler.
// Should only be called from the IO thread, since it accesses an
// net::URLRequest.
void ResetLoginHandlerForRequest(net::URLRequest* request) {
  ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
}

// Get the signon_realm under which this auth info should be stored.
//
// The format of the signon_realm for proxy auth is:
//     proxy-host/auth-realm
// The format of the signon_realm for server auth is:
//     url-scheme://url-host[:url-port]/auth-realm
//
// Be careful when changing this function, since you could make existing
// saved logins un-retrievable.
std::string GetSignonRealm(const GURL& url,
                           const net::AuthChallengeInfo& auth_info) {
  std::string signon_realm;
  if (auth_info.is_proxy) {
    signon_realm = auth_info.challenger.ToString();
    signon_realm.append("/");
  } else {
    // Take scheme, host, and port from the url.
    signon_realm = url.GetOrigin().spec();
    // This ends with a "/".
  }
  signon_realm.append(auth_info.realm);
  return signon_realm;
}

// ----------------------------------------------------------------------------
// LoginHandler

LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
                           net::URLRequest* request)
    : handled_auth_(false),
      auth_info_(auth_info),
      request_(request),
      http_network_session_(
          request_->context()->http_transaction_factory()->GetSession()),
      password_manager_(NULL),
      login_model_(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_) << "LoginHandler constructed with NULL request";
  DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";

  AddRef();  // matched by LoginHandler::ReleaseSoon().

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&LoginHandler::AddObservers, this));

  if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
          &render_process_host_id_,  &render_frame_id_)) {
    NOTREACHED();
  }
}

void LoginHandler::OnRequestCancelled() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
      "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();
}

void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
  password_form_ = form;
}

void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
  password_manager_ = password_manager;
}

WebContents* LoginHandler::GetWebContentsForLogin() const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
      render_process_host_id_, render_frame_id_);
  return WebContents::FromRenderFrameHost(rfh);
}

void LoginHandler::SetAuth(const base::string16& username,
                           const base::string16& password) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (TestAndSetAuthHandled())
    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_);
  }

  // Calling NotifyAuthSupplied() directly instead of posting a task
  // allows other LoginHandler instances to queue their
  // CloseContentsDeferred() before ours.  Closing dialogs in the
  // opposite order as they were created avoids races where remaining
  // dialogs in the same tab may be briefly displayed to the user
  // before they are removed.
  NotifyAuthSupplied(username, password);

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&LoginHandler::CloseContentsDeferred, this));
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
}

void LoginHandler::CancelAuth() {
  if (TestAndSetAuthHandled())
    return;

  // Similar to how we deal with notifications above in SetAuth()
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    NotifyAuthCancelled();
  } else {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&LoginHandler::NotifyAuthCancelled, this));
  }

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&LoginHandler::CloseContentsDeferred, this));
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(&LoginHandler::CancelAuthDeferred, this));
}


void LoginHandler::Observe(int type,
                           const content::NotificationSource& source,
                           const content::NotificationDetails& details) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
         type == chrome::NOTIFICATION_AUTH_CANCELLED);

  WebContents* requesting_contents = GetWebContentsForLogin();
  if (!requesting_contents)
    return;

  // Break out early if we aren't interested in the notification.
  if (WasAuthHandled())
    return;

  LoginNotificationDetails* login_details =
      content::Details<LoginNotificationDetails>(details).ptr();

  // WasAuthHandled() should always test positive before we publish
  // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
  DCHECK(login_details->handler() != this);

  // Only handle notification for the identical auth info.
  if (!login_details->handler()->auth_info()->Equals(*auth_info()))
    return;

  // Ignore login notification events from other profiles.
  if (login_details->handler()->http_network_session_ !=
      http_network_session_)
    return;

  // Set or cancel the auth in this handler.
  if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
    AuthSuppliedLoginNotificationDetails* supplied_details =
        content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
    SetAuth(supplied_details->username(), supplied_details->password());
  } else {
    DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
    CancelAuth();
  }
}

// Returns whether authentication had been handled (SetAuth or CancelAuth).
bool LoginHandler::WasAuthHandled() const {
  base::AutoLock lock(handled_auth_lock_);
  bool was_handled = handled_auth_;
  return was_handled;
}

LoginHandler::~LoginHandler() {
  SetModel(NULL);
}

void LoginHandler::SetModel(LoginModel* model) {
  if (login_model_)
    login_model_->RemoveObserver(this);
  login_model_ = model;
  if (login_model_)
    login_model_->AddObserver(this);
}

void LoginHandler::NotifyAuthNeeded() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (WasAuthHandled())
    return;

  content::NotificationService* service =
      content::NotificationService::current();
  NavigationController* controller = NULL;

  WebContents* requesting_contents = GetWebContentsForLogin();
  if (requesting_contents)
    controller = &requesting_contents->GetController();

  LoginNotificationDetails details(this);

  service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
                  content::Source<NavigationController>(controller),
                  content::Details<LoginNotificationDetails>(&details));
}

void LoginHandler::ReleaseSoon() {
  if (!TestAndSetAuthHandled()) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&LoginHandler::CancelAuthDeferred, this));
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&LoginHandler::NotifyAuthCancelled, this));
  }

  BrowserThread::PostTask(
    BrowserThread::UI, FROM_HERE,
    base::Bind(&LoginHandler::RemoveObservers, this));

  // Delete this object once all InvokeLaters have been called.
  BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
}

void LoginHandler::AddObservers() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // This is probably OK; we need to listen to everything and we break out of
  // the Observe() if we aren't handling the same auth_info().
  registrar_.reset(new content::NotificationRegistrar);
  registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
                  content::NotificationService::AllBrowserContextsAndSources());
  registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
                  content::NotificationService::AllBrowserContextsAndSources());
}

void LoginHandler::RemoveObservers() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  registrar_.reset();
}

void LoginHandler::NotifyAuthSupplied(const base::string16& username,
                                      const base::string16& password) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(WasAuthHandled());

  WebContents* requesting_contents = GetWebContentsForLogin();
  if (!requesting_contents)
    return;

  content::NotificationService* service =
      content::NotificationService::current();
  NavigationController* controller =
      &requesting_contents->GetController();
  AuthSuppliedLoginNotificationDetails details(this, username, password);

  service->Notify(
      chrome::NOTIFICATION_AUTH_SUPPLIED,
      content::Source<NavigationController>(controller),
      content::Details<AuthSuppliedLoginNotificationDetails>(&details));
}

void LoginHandler::NotifyAuthCancelled() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(WasAuthHandled());

  content::NotificationService* service =
      content::NotificationService::current();
  NavigationController* controller = NULL;

  WebContents* requesting_contents = GetWebContentsForLogin();
  if (requesting_contents)
    controller = &requesting_contents->GetController();

  LoginNotificationDetails details(this);

  service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
                  content::Source<NavigationController>(controller),
                  content::Details<LoginNotificationDetails>(&details));
}

// Marks authentication as handled and returns the previous handled state.
bool LoginHandler::TestAndSetAuthHandled() {
  base::AutoLock lock(handled_auth_lock_);
  bool was_handled = handled_auth_;
  handled_auth_ = true;
  return was_handled;
}

// Calls SetAuth from the IO loop.
void LoginHandler::SetAuthDeferred(const base::string16& username,
                                   const base::string16& password) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  if (request_) {
    request_->SetAuth(net::AuthCredentials(username, password));
    ResetLoginHandlerForRequest(request_);
  }
}

// Calls CancelAuth from the IO loop.
void LoginHandler::CancelAuthDeferred() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  if (request_) {
    request_->CancelAuth();
    // Verify that CancelAuth doesn't destroy the request via our delegate.
    DCHECK(request_ != NULL);
    ResetLoginHandlerForRequest(request_);
  }
}

// Closes the view_contents from the UI loop.
void LoginHandler::CloseContentsDeferred() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  CloseDialog();
}

// Helper to create a PasswordForm and stuff it into a vector as input
// for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
void MakeInputForPasswordManager(
    const GURL& request_url,
    net::AuthChallengeInfo* auth_info,
    LoginHandler* handler,
    std::vector<PasswordForm>* password_manager_input) {
  PasswordForm dialog_form;
  if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
    dialog_form.scheme = PasswordForm::SCHEME_BASIC;
  } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
    dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
  } else {
    dialog_form.scheme = PasswordForm::SCHEME_OTHER;
  }
  std::string host_and_port(auth_info->challenger.ToString());
  if (auth_info->is_proxy) {
    std::string origin = host_and_port;
    // We don't expect this to already start with http:// or https://.
    DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
    origin = std::string("http://") + origin;
    dialog_form.origin = GURL(origin);
  } else if (!auth_info->challenger.Equals(
      net::HostPortPair::FromURL(request_url))) {
    dialog_form.origin = GURL();
    NOTREACHED();  // crbug.com/32718
  } else {
    dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
  }
  dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
  password_manager_input->push_back(dialog_form);
  // Set the password form for the handler (by copy).
  handler->SetPasswordForm(dialog_form);
}

// This callback is run on the UI thread and creates a constrained window with
// a LoginView to prompt the user.  The response will be sent to LoginHandler,
// which then routes it to the net::URLRequest on the I/O thread.
void LoginDialogCallback(const GURL& request_url,
                         net::AuthChallengeInfo* auth_info,
                         LoginHandler* handler) {
  WebContents* parent_contents = handler->GetWebContentsForLogin();
  if (!parent_contents || handler->WasAuthHandled()) {
    // The request may have been cancelled, or it may be for a renderer
    // not hosted by a tab (e.g. an extension). Cancel just in case
    // (cancelling twice is a no-op).
    handler->CancelAuth();
    return;
  }

  prerender::PrerenderContents* prerender_contents =
      prerender::PrerenderContents::FromWebContents(parent_contents);
  if (prerender_contents) {
    prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
    return;
  }

  PasswordManager* password_manager =
      ChromePasswordManagerClient::GetManagerFromWebContents(parent_contents);
  if (!password_manager) {
    // Same logic as above.
    handler->CancelAuth();
    return;
  }

  // Tell the password manager to look for saved passwords.
  std::vector<PasswordForm> v;
  MakeInputForPasswordManager(request_url, auth_info, handler, &v);
  password_manager->OnPasswordFormsParsed(v);
  handler->SetPasswordManager(password_manager);

  // The realm is controlled by the remote server, so there is no reason
  // to believe it is of a reasonable length.
  base::string16 elided_realm;
  gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);

  base::string16 host_and_port = base::ASCIIToUTF16(
      request_url.scheme() + "://" + auth_info->challenger.ToString());
  base::string16 explanation = elided_realm.empty() ?
      l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
                                 host_and_port) :
      l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
                                 host_and_port,
                                 elided_realm);
  handler->BuildViewForPasswordManager(password_manager, explanation);
}

// ----------------------------------------------------------------------------
// Public API

LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
                                net::URLRequest* request) {
  LoginHandler* handler = LoginHandler::Create(auth_info, request);
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&LoginDialogCallback, request->url(),
                 make_scoped_refptr(auth_info), make_scoped_refptr(handler)));
  return handler;
}

/* [<][>][^][v][top][bottom][index][help] */