password_generation_agent.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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/logging.h"
8#include "base/memory/scoped_ptr.h"
9#include "components/autofill/content/renderer/password_form_conversion_utils.h"
10#include "components/autofill/core/common/autofill_messages.h"
11#include "components/autofill/core/common/form_data.h"
12#include "components/autofill/core/common/password_form.h"
13#include "components/autofill/core/common/password_generation_util.h"
14#include "content/public/renderer/render_view.h"
15#include "google_apis/gaia/gaia_urls.h"
16#include "third_party/WebKit/public/platform/WebCString.h"
17#include "third_party/WebKit/public/platform/WebRect.h"
18#include "third_party/WebKit/public/platform/WebVector.h"
19#include "third_party/WebKit/public/web/WebDocument.h"
20#include "third_party/WebKit/public/web/WebFormElement.h"
21#include "third_party/WebKit/public/web/WebFrame.h"
22#include "third_party/WebKit/public/web/WebInputElement.h"
23#include "third_party/WebKit/public/web/WebSecurityOrigin.h"
24#include "third_party/WebKit/public/web/WebView.h"
25#include "ui/gfx/rect.h"
26
27namespace autofill {
28
29namespace {
30
31// Returns true if we think that this form is for account creation. |passwords|
32// is filled with the password field(s) in the form.
33bool GetAccountCreationPasswordFields(
34    const WebKit::WebFormElement& form,
35    std::vector<WebKit::WebInputElement>* passwords) {
36  // Grab all of the passwords for the form.
37  WebKit::WebVector<WebKit::WebFormControlElement> control_elements;
38  form.getFormControlElements(control_elements);
39
40  size_t num_input_elements = 0;
41  for (size_t i = 0; i < control_elements.size(); i++) {
42    WebKit::WebInputElement* input_element =
43        toWebInputElement(&control_elements[i]);
44    // Only pay attention to visible password fields.
45    if (input_element &&
46        input_element->isTextField() &&
47        input_element->hasNonEmptyBoundingBox()) {
48      num_input_elements++;
49      if (input_element->isPasswordField())
50        passwords->push_back(*input_element);
51    }
52  }
53
54  // This may be too lenient, but we assume that any form with at least three
55  // input elements where at least one of them is a password is an account
56  // creation form.
57  if (!passwords->empty() && num_input_elements >= 3) {
58    // We trim |passwords| because occasionally there are forms where the
59    // security question answers are put in password fields and we don't want
60    // to fill those.
61    if (passwords->size() > 2)
62      passwords->resize(2);
63
64    return true;
65  }
66
67  return false;
68}
69
70bool ContainsURL(const std::vector<GURL>& urls, const GURL& url) {
71  return std::find(urls.begin(), urls.end(), url) != urls.end();
72}
73
74// Returns true if the |form1| is essentially equal to |form2|.
75bool FormEquals(const autofill::FormData& form1,
76                const PasswordForm& form2) {
77  // TODO(zysxqn): use more signals than just origin to compare.
78  return form1.origin == form2.origin;
79}
80
81bool ContainsForm(const std::vector<autofill::FormData>& forms,
82                  const PasswordForm& form) {
83  for (std::vector<autofill::FormData>::const_iterator it =
84           forms.begin(); it != forms.end(); ++it) {
85    if (FormEquals(*it, form))
86      return true;
87  }
88  return false;
89}
90
91}  // namespace
92
93PasswordGenerationAgent::PasswordGenerationAgent(
94    content::RenderView* render_view)
95    : content::RenderViewObserver(render_view),
96      render_view_(render_view),
97      enabled_(false) {
98  render_view_->GetWebView()->setPasswordGeneratorClient(this);
99}
100PasswordGenerationAgent::~PasswordGenerationAgent() {}
101
102void PasswordGenerationAgent::DidFinishDocumentLoad(WebKit::WebFrame* frame) {
103  // In every navigation, the IPC message sent by the password autofill manager
104  // to query whether the current form is blacklisted or not happens when the
105  // document load finishes, so we need to clear previous states here before we
106  // hear back from the browser. We only clear this state on main frame load
107  // as we don't want subframe loads to clear state that we have recieved from
108  // the main frame. Note that we assume there is only one account creation
109  // form, but there could be multiple password forms in each frame.
110  //
111  // TODO(zysxqn): Add stat when local heuristic fires but we don't show the
112  // password generation icon.
113  if (!frame->parent()) {
114    not_blacklisted_password_form_origins_.clear();
115    account_creation_forms_.clear();
116    possible_account_creation_form_.reset(new PasswordForm());
117    passwords_.clear();
118  }
119}
120
121void PasswordGenerationAgent::DidFinishLoad(WebKit::WebFrame* frame) {
122  // We don't want to generate passwords if the browser won't store or sync
123  // them.
124  if (!enabled_)
125    return;
126
127  if (!ShouldAnalyzeDocument(frame->document()))
128    return;
129
130  WebKit::WebVector<WebKit::WebFormElement> forms;
131  frame->document().forms(forms);
132  for (size_t i = 0; i < forms.size(); ++i) {
133    if (forms[i].isNull())
134      continue;
135
136    // If we can't get a valid PasswordForm, we skip this form because the
137    // the password won't get saved even if we generate it.
138    scoped_ptr<PasswordForm> password_form(
139        CreatePasswordForm(forms[i]));
140    if (!password_form.get()) {
141      DVLOG(2) << "Skipping form as it would not be saved";
142      continue;
143    }
144
145    // Do not generate password for GAIA since it is used to retrieve the
146    // generated paswords.
147    GURL realm(password_form->signon_realm);
148    if (realm == GaiaUrls::GetInstance()->gaia_login_form_realm())
149      continue;
150
151    std::vector<WebKit::WebInputElement> passwords;
152    if (GetAccountCreationPasswordFields(forms[i], &passwords)) {
153      DVLOG(2) << "Account creation form detected";
154      password_generation::LogPasswordGenerationEvent(
155          password_generation::SIGN_UP_DETECTED);
156      passwords_ = passwords;
157      possible_account_creation_form_.swap(password_form);
158      MaybeShowIcon();
159      // We assume that there is only one account creation field per URL.
160      return;
161    }
162  }
163  password_generation::LogPasswordGenerationEvent(
164      password_generation::NO_SIGN_UP_DETECTED);
165}
166
167bool PasswordGenerationAgent::ShouldAnalyzeDocument(
168    const WebKit::WebDocument& document) const {
169  // Make sure that this security origin is allowed to use password manager.
170  // Generating a password that can't be saved is a bad idea.
171  WebKit::WebSecurityOrigin origin = document.securityOrigin();
172  if (!origin.canAccessPasswordManager()) {
173    DVLOG(1) << "No PasswordManager access";
174    return false;
175  }
176
177  return true;
178}
179
180void PasswordGenerationAgent::openPasswordGenerator(
181    WebKit::WebInputElement& element) {
182  WebKit::WebElement button(element.passwordGeneratorButtonElement());
183  gfx::Rect rect(button.boundsInViewportSpace());
184  scoped_ptr<PasswordForm> password_form(
185      CreatePasswordForm(element.form()));
186  // We should not have shown the icon we can't create a valid PasswordForm.
187  DCHECK(password_form.get());
188
189  Send(new AutofillHostMsg_ShowPasswordGenerationPopup(routing_id(),
190                                                       rect,
191                                                       element.maxLength(),
192                                                       *password_form));
193  password_generation::LogPasswordGenerationEvent(
194      password_generation::BUBBLE_SHOWN);
195}
196
197bool PasswordGenerationAgent::OnMessageReceived(const IPC::Message& message) {
198  bool handled = true;
199  IPC_BEGIN_MESSAGE_MAP(PasswordGenerationAgent, message)
200    IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted,
201                        OnFormNotBlacklisted)
202    IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted,
203                        OnPasswordAccepted)
204    IPC_MESSAGE_HANDLER(AutofillMsg_PasswordGenerationEnabled,
205                        OnPasswordGenerationEnabled)
206    IPC_MESSAGE_HANDLER(AutofillMsg_AccountCreationFormsDetected,
207                        OnAccountCreationFormsDetected)
208    IPC_MESSAGE_UNHANDLED(handled = false)
209  IPC_END_MESSAGE_MAP()
210  return handled;
211}
212
213void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm& form) {
214  not_blacklisted_password_form_origins_.push_back(form.origin);
215  MaybeShowIcon();
216}
217
218void PasswordGenerationAgent::OnPasswordAccepted(
219    const base::string16& password) {
220  for (std::vector<WebKit::WebInputElement>::iterator it = passwords_.begin();
221       it != passwords_.end(); ++it) {
222    it->setValue(password);
223    it->setAutofilled(true);
224    // Advance focus to the next input field. We assume password fields in
225    // an account creation form are always adjacent.
226    render_view_->GetWebView()->advanceFocus(false);
227  }
228}
229
230void PasswordGenerationAgent::OnPasswordGenerationEnabled(bool enabled) {
231  enabled_ = enabled;
232}
233
234void PasswordGenerationAgent::OnAccountCreationFormsDetected(
235    const std::vector<autofill::FormData>& forms) {
236  account_creation_forms_.insert(
237      account_creation_forms_.end(), forms.begin(), forms.end());
238  MaybeShowIcon();
239}
240
241void PasswordGenerationAgent::MaybeShowIcon() {
242  // We should show the password generation icon only when we have detected
243  // account creation form, we have confirmed from browser that this form
244  // is not blacklisted by the users, and the Autofill server has marked one
245  // of its field as ACCOUNT_CREATION_PASSWORD.
246  if (!possible_account_creation_form_.get() ||
247      passwords_.empty() ||
248      not_blacklisted_password_form_origins_.empty() ||
249      account_creation_forms_.empty()) {
250    return;
251  }
252
253  if (!ContainsURL(not_blacklisted_password_form_origins_,
254                   possible_account_creation_form_->origin)) {
255    return;
256  }
257
258  if (!ContainsForm(account_creation_forms_,
259                    *possible_account_creation_form_)) {
260    return;
261  }
262
263  passwords_[0].passwordGeneratorButtonElement().setAttribute("style",
264                                                              "display:block");
265  password_generation::LogPasswordGenerationEvent(
266      password_generation::ICON_SHOWN);
267}
268
269}  // namespace autofill
270