Home | History | Annotate | Download | only in passwords
      1 // Copyright 2014 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/passwords/manage_passwords_ui_controller.h"
      6 
      7 #include "chrome/app/chrome_command_ids.h"
      8 #include "chrome/browser/browsing_data/browsing_data_helper.h"
      9 #include "chrome/browser/chrome_notification_types.h"
     10 #include "chrome/browser/password_manager/password_store_factory.h"
     11 #include "chrome/browser/ui/browser_command_controller.h"
     12 #include "chrome/browser/ui/browser_finder.h"
     13 #include "chrome/browser/ui/browser_window.h"
     14 #include "chrome/browser/ui/chrome_pages.h"
     15 #include "chrome/browser/ui/omnibox/location_bar.h"
     16 #include "chrome/browser/ui/passwords/manage_passwords_icon.h"
     17 #include "chrome/common/url_constants.h"
     18 #include "components/password_manager/core/browser/password_store.h"
     19 #include "content/public/browser/notification_service.h"
     20 
     21 using autofill::PasswordFormMap;
     22 using password_manager::PasswordFormManager;
     23 
     24 namespace {
     25 
     26 password_manager::PasswordStore* GetPasswordStore(
     27     content::WebContents* web_contents) {
     28   return PasswordStoreFactory::GetForProfile(
     29              Profile::FromBrowserContext(web_contents->GetBrowserContext()),
     30              Profile::EXPLICIT_ACCESS).get();
     31 }
     32 
     33 } // namespace
     34 
     35 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController);
     36 
     37 ManagePasswordsUIController::ManagePasswordsUIController(
     38     content::WebContents* web_contents)
     39     : content::WebContentsObserver(web_contents),
     40       state_(password_manager::ui::INACTIVE_STATE) {
     41   password_manager::PasswordStore* password_store =
     42       GetPasswordStore(web_contents);
     43   if (password_store)
     44     password_store->AddObserver(this);
     45 }
     46 
     47 ManagePasswordsUIController::~ManagePasswordsUIController() {}
     48 
     49 void ManagePasswordsUIController::UpdateBubbleAndIconVisibility() {
     50   // If we're not on a "webby" URL (e.g. "chrome://sign-in"), we shouldn't
     51   // display either the bubble or the icon.
     52   if (!BrowsingDataHelper::IsWebScheme(
     53           web_contents()->GetLastCommittedURL().scheme())) {
     54     state_ = password_manager::ui::INACTIVE_STATE;
     55   }
     56 
     57   #if !defined(OS_ANDROID)
     58     Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
     59     if (!browser)
     60       return;
     61     LocationBar* location_bar = browser->window()->GetLocationBar();
     62     DCHECK(location_bar);
     63     location_bar->UpdateManagePasswordsIconAndBubble();
     64   #endif
     65 }
     66 
     67 void ManagePasswordsUIController::OnPasswordSubmitted(
     68     PasswordFormManager* form_manager) {
     69   form_manager_.reset(form_manager);
     70   password_form_map_ = form_manager_->best_matches();
     71   origin_ = PendingCredentials().origin;
     72   state_ = password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE;
     73   UpdateBubbleAndIconVisibility();
     74 }
     75 
     76 void ManagePasswordsUIController::OnPasswordAutofilled(
     77     const PasswordFormMap& password_form_map) {
     78   password_form_map_ = password_form_map;
     79   origin_ = password_form_map_.begin()->second->origin;
     80   state_ = password_manager::ui::MANAGE_STATE;
     81   UpdateBubbleAndIconVisibility();
     82 }
     83 
     84 void ManagePasswordsUIController::OnBlacklistBlockedAutofill(
     85     const PasswordFormMap& password_form_map) {
     86   password_form_map_ = password_form_map;
     87   origin_ = password_form_map_.begin()->second->origin;
     88   state_ = password_manager::ui::BLACKLIST_STATE;
     89   UpdateBubbleAndIconVisibility();
     90 }
     91 
     92 void ManagePasswordsUIController::WebContentsDestroyed() {
     93   password_manager::PasswordStore* password_store =
     94       GetPasswordStore(web_contents());
     95   if (password_store)
     96     password_store->RemoveObserver(this);
     97 }
     98 
     99 void ManagePasswordsUIController::OnLoginsChanged(
    100     const password_manager::PasswordStoreChangeList& changes) {
    101   password_manager::ui::State current_state = state_;
    102   for (password_manager::PasswordStoreChangeList::const_iterator it =
    103            changes.begin();
    104        it != changes.end();
    105        it++) {
    106     const autofill::PasswordForm& changed_form = it->form();
    107     if (changed_form.origin != origin_)
    108       continue;
    109 
    110     if (it->type() == password_manager::PasswordStoreChange::REMOVE) {
    111       password_form_map_.erase(changed_form.username_value);
    112       if (changed_form.blacklisted_by_user)
    113         state_ = password_manager::ui::MANAGE_STATE;
    114     } else {
    115       new_password_forms_.push_back(new autofill::PasswordForm(changed_form));
    116       password_form_map_[changed_form.username_value] =
    117           new_password_forms_.back();
    118       if (changed_form.blacklisted_by_user)
    119         state_ = password_manager::ui::BLACKLIST_STATE;
    120     }
    121   }
    122   if (current_state != state_)
    123     UpdateBubbleAndIconVisibility();
    124 }
    125 
    126 void ManagePasswordsUIController::
    127     NavigateToPasswordManagerSettingsPage() {
    128 // TODO(mkwst): chrome_pages.h is compiled out of Android. Need to figure out
    129 // how this navigation should work there.
    130 #if !defined(OS_ANDROID)
    131   chrome::ShowSettingsSubPage(
    132       chrome::FindBrowserWithWebContents(web_contents()),
    133       chrome::kPasswordManagerSubPage);
    134 #endif
    135 }
    136 
    137 void ManagePasswordsUIController::SavePassword() {
    138   DCHECK(PasswordPendingUserDecision());
    139   DCHECK(form_manager_.get());
    140   form_manager_->Save();
    141   state_ = password_manager::ui::MANAGE_STATE;
    142 }
    143 
    144 void ManagePasswordsUIController::NeverSavePassword() {
    145   DCHECK(PasswordPendingUserDecision());
    146   DCHECK(form_manager_.get());
    147   form_manager_->PermanentlyBlacklist();
    148   state_ = password_manager::ui::BLACKLIST_STATE;
    149   UpdateBubbleAndIconVisibility();
    150 }
    151 
    152 void ManagePasswordsUIController::UnblacklistSite() {
    153   // We're in one of two states: either the user _just_ blacklisted the site
    154   // by clicking "Never save" in the pending bubble, or the user is visiting
    155   // a blacklisted site.
    156   //
    157   // Either way, |password_form_map_| has been populated with the relevant
    158   // form. We can safely pull it out, send it over to the password store
    159   // for removal, and update our internal state.
    160   DCHECK(!password_form_map_.empty());
    161   DCHECK(password_form_map_.begin()->second);
    162   DCHECK(state_ == password_manager::ui::BLACKLIST_STATE);
    163   password_manager::PasswordStore* password_store =
    164       GetPasswordStore(web_contents());
    165   if (password_store)
    166     password_store->RemoveLogin(*password_form_map_.begin()->second);
    167   state_ = password_manager::ui::MANAGE_STATE;
    168   UpdateBubbleAndIconVisibility();
    169 }
    170 
    171 void ManagePasswordsUIController::DidNavigateMainFrame(
    172     const content::LoadCommittedDetails& details,
    173     const content::FrameNavigateParams& params) {
    174   // Don't react to in-page (fragment) navigations.
    175   if (details.is_in_page)
    176     return;
    177 
    178   // Don't do anything if a navigation occurs before a user could reasonably
    179   // interact with the password bubble.
    180   if (timer_ && timer_->Elapsed() < base::TimeDelta::FromSeconds(1))
    181     return;
    182 
    183   // Otherwise, reset the password manager and the timer.
    184   state_ = password_manager::ui::INACTIVE_STATE;
    185   UpdateBubbleAndIconVisibility();
    186   timer_.reset(new base::ElapsedTimer());
    187 }
    188 
    189 const autofill::PasswordForm& ManagePasswordsUIController::
    190     PendingCredentials() const {
    191   DCHECK(form_manager_);
    192   return form_manager_->pending_credentials();
    193 }
    194 
    195 void ManagePasswordsUIController::UpdateIconAndBubbleState(
    196     ManagePasswordsIcon* icon) {
    197   if (state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE) {
    198     // We must display the icon before showing the bubble, as the bubble would
    199     // be otherwise unanchored. However, we can't change the controller's state
    200     // until _after_ the bubble is shown, as our metrics depend on the
    201     // distinction between PENDING_PASSWORD_AND_BUBBLE_STATE and
    202     // PENDING_PASSWORD_STATE to determine if the bubble opened automagically
    203     // or via user action.
    204     icon->SetState(password_manager::ui::PENDING_PASSWORD_STATE);
    205     ShowBubbleWithoutUserInteraction();
    206     state_ = password_manager::ui::PENDING_PASSWORD_STATE;
    207   } else  {
    208     icon->SetState(state_);
    209   }
    210 }
    211 
    212 void ManagePasswordsUIController::ShowBubbleWithoutUserInteraction() {
    213   DCHECK_EQ(state_, password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE);
    214 #if !defined(OS_ANDROID)
    215   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
    216   if (!browser || browser->toolbar_model()->input_in_progress())
    217     return;
    218   CommandUpdater* updater = browser->command_controller()->command_updater();
    219   updater->ExecuteCommand(IDC_MANAGE_PASSWORDS_FOR_PAGE);
    220 #endif
    221 }
    222 
    223 bool ManagePasswordsUIController::PasswordPendingUserDecision() const {
    224   return state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
    225          state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE;
    226 }
    227