password_generation_agent.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
1// Copyright 2013 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 "components/autofill/content/renderer/password_generation_agent.h"
6
7#include "base/command_line.h"
8#include "base/logging.h"
9#include "base/memory/scoped_ptr.h"
10#include "components/autofill/content/common/autofill_messages.h"
11#include "components/autofill/content/renderer/form_autofill_util.h"
12#include "components/autofill/content/renderer/password_form_conversion_utils.h"
13#include "components/autofill/core/common/autofill_switches.h"
14#include "components/autofill/core/common/form_data.h"
15#include "components/autofill/core/common/password_form.h"
16#include "components/autofill/core/common/password_generation_util.h"
17#include "content/public/renderer/render_view.h"
18#include "google_apis/gaia/gaia_urls.h"
19#include "third_party/WebKit/public/platform/WebCString.h"
20#include "third_party/WebKit/public/platform/WebRect.h"
21#include "third_party/WebKit/public/platform/WebVector.h"
22#include "third_party/WebKit/public/web/WebDocument.h"
23#include "third_party/WebKit/public/web/WebFormElement.h"
24#include "third_party/WebKit/public/web/WebInputElement.h"
25#include "third_party/WebKit/public/web/WebLocalFrame.h"
26#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
27#include "third_party/WebKit/public/web/WebView.h"
28#include "ui/gfx/rect.h"
29
30namespace autofill {
31
32namespace {
33
34// Returns true if we think that this form is for account creation. |passwords|
35// is filled with the password field(s) in the form.
36bool GetAccountCreationPasswordFields(
37    const blink::WebFormElement& form,
38    std::vector<blink::WebInputElement>* passwords) {
39  // Grab all of the passwords for the form.
40  blink::WebVector<blink::WebFormControlElement> control_elements;
41  form.getFormControlElements(control_elements);
42
43  size_t num_input_elements = 0;
44  for (size_t i = 0; i < control_elements.size(); i++) {
45    blink::WebInputElement* input_element =
46        toWebInputElement(&control_elements[i]);
47    // Only pay attention to visible password fields.
48    if (input_element &&
49        input_element->isTextField() &&
50        input_element->hasNonEmptyBoundingBox()) {
51      num_input_elements++;
52      if (input_element->isPasswordField())
53        passwords->push_back(*input_element);
54    }
55  }
56
57  // This may be too lenient, but we assume that any form with at least three
58  // input elements where at least one of them is a password is an account
59  // creation form.
60  if (!passwords->empty() && num_input_elements >= 3) {
61    // We trim |passwords| because occasionally there are forms where the
62    // security question answers are put in password fields and we don't want
63    // to fill those.
64    if (passwords->size() > 2)
65      passwords->resize(2);
66
67    return true;
68  }
69
70  return false;
71}
72
73bool ContainsURL(const std::vector<GURL>& urls, const GURL& url) {
74  return std::find(urls.begin(), urls.end(), url) != urls.end();
75}
76
77// Returns true if the |form1| is essentially equal to |form2|.
78bool FormsAreEqual(const autofill::FormData& form1,
79                   const PasswordForm& form2) {
80  // TODO(zysxqn): use more signals than just origin to compare.
81  // Note that FormData strips the fragement from the url while PasswordForm
82  // strips both the fragement and the path, so we can't just compare these
83  // two directly.
84  return form1.origin.GetOrigin() == form2.origin.GetOrigin();
85}
86
87bool ContainsForm(const std::vector<autofill::FormData>& forms,
88                  const PasswordForm& form) {
89  for (std::vector<autofill::FormData>::const_iterator it =
90           forms.begin(); it != forms.end(); ++it) {
91    if (FormsAreEqual(*it, form))
92      return true;
93  }
94  return false;
95}
96
97}  // namespace
98
99PasswordGenerationAgent::PasswordGenerationAgent(
100    content::RenderView* render_view)
101    : content::RenderViewObserver(render_view),
102      render_view_(render_view),
103      password_is_generated_(false),
104      password_edited_(false),
105      enabled_(password_generation::IsPasswordGenerationEnabled()) {
106  DVLOG(2) << "Password Generation is " << (enabled_ ? "Enabled" : "Disabled");
107}
108PasswordGenerationAgent::~PasswordGenerationAgent() {}
109
110void PasswordGenerationAgent::DidFinishDocumentLoad(
111    blink::WebLocalFrame* frame) {
112  // In every navigation, the IPC message sent by the password autofill manager
113  // to query whether the current form is blacklisted or not happens when the
114  // document load finishes, so we need to clear previous states here before we
115  // hear back from the browser. We only clear this state on main frame load
116  // as we don't want subframe loads to clear state that we have received from
117  // the main frame. Note that we assume there is only one account creation
118  // form, but there could be multiple password forms in each frame.
119  if (!frame->parent()) {
120    not_blacklisted_password_form_origins_.clear();
121    generation_enabled_forms_.clear();
122    generation_element_.reset();
123    possible_account_creation_form_.reset(new PasswordForm());
124    password_elements_.clear();
125    password_is_generated_ = false;
126    if (password_edited_) {
127      password_generation::LogPasswordGenerationEvent(
128          password_generation::PASSWORD_EDITED);
129    }
130    password_edited_ = false;
131  }
132}
133
134void PasswordGenerationAgent::DidFinishLoad(blink::WebLocalFrame* frame) {
135  if (!enabled_)
136    return;
137
138  // We don't want to generate passwords if the browser won't store or sync
139  // them.
140  if (!ShouldAnalyzeDocument(frame->document()))
141    return;
142
143  blink::WebVector<blink::WebFormElement> forms;
144  frame->document().forms(forms);
145  for (size_t i = 0; i < forms.size(); ++i) {
146    if (forms[i].isNull())
147      continue;
148
149    // If we can't get a valid PasswordForm, we skip this form because the
150    // the password won't get saved even if we generate it.
151    scoped_ptr<PasswordForm> password_form(
152        CreatePasswordForm(forms[i]));
153    if (!password_form.get()) {
154      DVLOG(2) << "Skipping form as it would not be saved";
155      continue;
156    }
157
158    // Do not generate password for GAIA since it is used to retrieve the
159    // generated paswords.
160    GURL realm(password_form->signon_realm);
161    if (realm == GaiaUrls::GetInstance()->gaia_login_form_realm())
162      continue;
163
164    std::vector<blink::WebInputElement> passwords;
165    if (GetAccountCreationPasswordFields(forms[i], &passwords)) {
166      DVLOG(2) << "Account creation form detected";
167      password_generation::LogPasswordGenerationEvent(
168          password_generation::SIGN_UP_DETECTED);
169      password_elements_ = passwords;
170      possible_account_creation_form_.swap(password_form);
171      DetermineGenerationElement();
172      // We assume that there is only one account creation field per URL.
173      return;
174    }
175  }
176  password_generation::LogPasswordGenerationEvent(
177      password_generation::NO_SIGN_UP_DETECTED);
178}
179
180bool PasswordGenerationAgent::ShouldAnalyzeDocument(
181    const blink::WebDocument& document) const {
182  // Make sure that this security origin is allowed to use password manager.
183  // Generating a password that can't be saved is a bad idea.
184  blink::WebSecurityOrigin origin = document.securityOrigin();
185  if (!origin.canAccessPasswordManager()) {
186    DVLOG(1) << "No PasswordManager access";
187    return false;
188  }
189
190  return true;
191}
192
193bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message& message) {
194  bool handled = true;
195  IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent, message)
196    IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted,
197                        OnFormNotBlacklisted)
198    IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted,
199                        OnPasswordAccepted)
200    IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected,
201                        OnAccountCreationFormsDetected)
202    IPC_MESSAGE_UNHANDLED(handled = false)
203  IPC_END_MESSAGE_MAP()
204  return handled;
205}
206
207void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm& form) {
208  not_blacklisted_password_form_origins_.push_back(form.origin);
209  DetermineGenerationElement();
210}
211
212void PasswordGenerationAgent::OnPasswordAccepted(
213    const base::string16& password) {
214  password_is_generated_ = true;
215  password_generation::LogPasswordGenerationEvent(
216      password_generation::PASSWORD_ACCEPTED);
217  for (std::vector<blink::WebInputElement>::iterator it =
218           password_elements_.begin();
219       it != password_elements_.end(); ++it) {
220    it->setValue(password);
221    it->setAutofilled(true);
222    // Advance focus to the next input field. We assume password fields in
223    // an account creation form are always adjacent.
224    render_view_->GetWebView()->advanceFocus(false);
225  }
226}
227
228void PasswordGenerationAgent::OnAccountCreationFormsDetected(
229    const std::vector<autofill::FormData>& forms) {
230  generation_enabled_forms_.insert(
231      generation_enabled_forms_.end(), forms.begin(), forms.end());
232  DetermineGenerationElement();
233}
234
235void PasswordGenerationAgent::DetermineGenerationElement() {
236  // Make sure local heuristics have identified a possible account creation
237  // form.
238  if (!possible_account_creation_form_.get() || password_elements_.empty()) {
239    DVLOG(2) << "Local hueristics have not detected a possible account "
240             << "creation form";
241    return;
242  }
243
244  if (CommandLine::ForCurrentProcess()->HasSwitch(
245          switches::kLocalHeuristicsOnlyForPasswordGeneration)) {
246    DVLOG(2) << "Bypassing additional checks.";
247  } else if (not_blacklisted_password_form_origins_.empty() ||
248             !ContainsURL(not_blacklisted_password_form_origins_,
249                          possible_account_creation_form_->origin)) {
250    DVLOG(2) << "Have not received confirmation that password form isn't "
251             << "blacklisted";
252    return;
253  } else if (generation_enabled_forms_.empty() ||
254             !ContainsForm(generation_enabled_forms_,
255                           *possible_account_creation_form_)) {
256    // Note that this message will never be sent if this feature is disabled
257    // (e.g. Password saving is disabled).
258    DVLOG(2) << "Have not received confirmation from Autofill that form is "
259             << "used for account creation";
260    return;
261  }
262
263  DVLOG(2) << "Password generation eligible form found";
264  generation_element_ = password_elements_[0];
265  password_generation::LogPasswordGenerationEvent(
266      password_generation::GENERATION_AVAILABLE);
267}
268
269bool PasswordGenerationAgent::FocusedNodeHasChanged(
270    const blink::WebNode& node) {
271  if (!generation_element_.isNull())
272    generation_element_.setShouldRevealPassword(false);
273
274  if (node.isNull() || !node.isElementNode())
275    return false;
276
277  const blink::WebElement web_element = node.toConst<blink::WebElement>();
278  if (!web_element.document().frame())
279    return false;
280
281  const blink::WebInputElement* element = toWebInputElement(&web_element);
282  if (!element || *element != generation_element_)
283    return false;
284
285  if (password_is_generated_) {
286    generation_element_.setShouldRevealPassword(true);
287    ShowEditingPopup();
288    return true;
289  }
290
291  // Only trigger if the password field is empty.
292  if (!element->isReadOnly() &&
293      element->isEnabled() &&
294      element->value().isEmpty()) {
295    ShowGenerationPopup();
296    return true;
297  }
298
299  return false;
300}
301
302bool PasswordGenerationAgent::TextDidChangeInTextField(
303    const blink::WebInputElement& element) {
304  if (element != generation_element_)
305    return false;
306
307  if (element.value().isEmpty()) {
308    if (password_is_generated_) {
309      // User generated a password and then deleted it.
310      password_generation::LogPasswordGenerationEvent(
311          password_generation::PASSWORD_DELETED);
312    }
313
314    // Do not treat the password as generated.
315    // TODO(gcasto): Set PasswordForm::type in the browser to TYPE_NORMAL.
316    password_is_generated_ = false;
317    generation_element_.setShouldRevealPassword(false);
318
319    // Offer generation again.
320    ShowGenerationPopup();
321  } else if (!password_is_generated_) {
322    // User has rejected the feature and has started typing a password.
323    HidePopup();
324  } else {
325    password_edited_ = true;
326    // Mirror edits to any confirmation password fields.
327    for (std::vector<blink::WebInputElement>::iterator it =
328             password_elements_.begin();
329         it != password_elements_.end(); ++it) {
330      it->setValue(element.value());
331    }
332  }
333
334  return true;
335}
336
337void PasswordGenerationAgent::ShowGenerationPopup() {
338  gfx::RectF bounding_box_scaled =
339      GetScaledBoundingBox(render_view_->GetWebView()->pageScaleFactor(),
340                           &generation_element_);
341
342  Send(new AutofillHostMsg_ShowPasswordGenerationPopup(
343      routing_id(),
344      bounding_box_scaled,
345      generation_element_.maxLength(),
346      *possible_account_creation_form_));
347
348  password_generation::LogPasswordGenerationEvent(
349      password_generation::GENERATION_POPUP_SHOWN);
350}
351
352void PasswordGenerationAgent::ShowEditingPopup() {
353  gfx::RectF bounding_box_scaled =
354      GetScaledBoundingBox(render_view_->GetWebView()->pageScaleFactor(),
355                           &generation_element_);
356
357  Send(new AutofillHostMsg_ShowPasswordEditingPopup(
358      routing_id(),
359      bounding_box_scaled,
360      *possible_account_creation_form_));
361
362  password_generation::LogPasswordGenerationEvent(
363      password_generation::EDITING_POPUP_SHOWN);
364}
365
366void PasswordGenerationAgent::HidePopup() {
367  Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id()));
368}
369
370}  // namespace autofill
371