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
GetPasswordStore(content::WebContents * web_contents)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
ConstifyMap(const autofill::PasswordFormMap & map)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.
DeepCopyMap(const autofill::PasswordFormMap & map,autofill::ConstPasswordFormMap * ret,ScopedVector<autofill::PasswordForm> * deleter)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
ManagePasswordsUIController(content::WebContents * web_contents)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
~ManagePasswordsUIController()69 ManagePasswordsUIController::~ManagePasswordsUIController() {}
70
UpdateBubbleAndIconVisibility()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
OnPasswordSubmitted(scoped_ptr<PasswordFormManager> form_manager)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
OnAutomaticPasswordSave(scoped_ptr<PasswordFormManager> form_manager)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
OnPasswordAutofilled(const PasswordFormMap & password_form_map)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
OnBlacklistBlockedAutofill(const PasswordFormMap & password_form_map)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
WebContentsDestroyed()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
OnLoginsChanged(const password_manager::PasswordStoreChangeList & changes)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::
NavigateToPasswordManagerSettingsPage()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
SavePassword()170 void ManagePasswordsUIController::SavePassword() {
171 DCHECK(PasswordPendingUserDecision());
172 SavePasswordInternal();
173 state_ = password_manager::ui::MANAGE_STATE;
174 UpdateBubbleAndIconVisibility();
175 }
176
SavePasswordInternal()177 void ManagePasswordsUIController::SavePasswordInternal() {
178 DCHECK(form_manager_.get());
179 form_manager_->Save();
180 }
181
NeverSavePassword()182 void ManagePasswordsUIController::NeverSavePassword() {
183 DCHECK(PasswordPendingUserDecision());
184 NeverSavePasswordInternal();
185 state_ = password_manager::ui::BLACKLIST_STATE;
186 UpdateBubbleAndIconVisibility();
187 }
188
NeverSavePasswordInternal()189 void ManagePasswordsUIController::NeverSavePasswordInternal() {
190 DCHECK(form_manager_.get());
191 form_manager_->PermanentlyBlacklist();
192 }
193
UnblacklistSite()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
DidNavigateMainFrame(const content::LoadCommittedDetails & details,const content::FrameNavigateParams & params)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
WasHidden()231 void ManagePasswordsUIController::WasHidden() {
232 #if !defined(OS_ANDROID)
233 chrome::CloseManagePasswordsBubble(web_contents());
234 #endif
235 }
236
237 const autofill::PasswordForm& ManagePasswordsUIController::
PendingCredentials() const238 PendingCredentials() const {
239 DCHECK(form_manager_);
240 return form_manager_->pending_credentials();
241 }
242
UpdateIconAndBubbleState(ManagePasswordsIcon * icon)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
ShowBubbleWithoutUserInteraction()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
PasswordPendingUserDecision() const272 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