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
22using autofill::PasswordFormMap;
23using password_manager::PasswordFormManager;
24
25namespace {
26
27password_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
34autofill::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.
43void 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
57DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController);
58
59ManagePasswordsUIController::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
69ManagePasswordsUIController::~ManagePasswordsUIController() {}
70
71void 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
89void 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
98void 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
109void 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
117void 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
125void ManagePasswordsUIController::WebContentsDestroyed() {
126  password_manager::PasswordStore* password_store =
127      GetPasswordStore(web_contents());
128  if (password_store)
129    password_store->RemoveObserver(this);
130}
131
132void 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
159void 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
170void ManagePasswordsUIController::SavePassword() {
171  DCHECK(PasswordPendingUserDecision());
172  SavePasswordInternal();
173  state_ = password_manager::ui::MANAGE_STATE;
174  UpdateBubbleAndIconVisibility();
175}
176
177void ManagePasswordsUIController::SavePasswordInternal() {
178  DCHECK(form_manager_.get());
179  form_manager_->Save();
180}
181
182void ManagePasswordsUIController::NeverSavePassword() {
183  DCHECK(PasswordPendingUserDecision());
184  NeverSavePasswordInternal();
185  state_ = password_manager::ui::BLACKLIST_STATE;
186  UpdateBubbleAndIconVisibility();
187}
188
189void ManagePasswordsUIController::NeverSavePasswordInternal() {
190  DCHECK(form_manager_.get());
191  form_manager_->PermanentlyBlacklist();
192}
193
194void 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
213void 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
231void ManagePasswordsUIController::WasHidden() {
232#if !defined(OS_ANDROID)
233  chrome::CloseManagePasswordsBubble(web_contents());
234#endif
235}
236
237const autofill::PasswordForm& ManagePasswordsUIController::
238    PendingCredentials() const {
239  DCHECK(form_manager_);
240  return form_manager_->pending_credentials();
241}
242
243void 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
261void 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
272bool ManagePasswordsUIController::PasswordPendingUserDecision() const {
273  return state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
274         state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE;
275}
276