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