Home | History | Annotate | Download | only in renderer
      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 
     30 namespace autofill {
     31 
     32 namespace {
     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.
     36 bool 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 
     73 bool 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|.
     78 bool 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 
     87 bool 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 
     99 PasswordGenerationAgent::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 }
    108 PasswordGenerationAgent::~PasswordGenerationAgent() {}
    109 
    110 void 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 
    134 void 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 
    180 bool 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 
    193 bool 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 
    207 void PasswordGenerationAgent::OnFormNotBlacklisted(const PasswordForm& form) {
    208   not_blacklisted_password_form_origins_.push_back(form.origin);
    209   DetermineGenerationElement();
    210 }
    211 
    212 void 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 
    228 void 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 
    235 void 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 
    269 bool 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   // Assume that if the password field has less than kMaximumOfferSize
    292   // characters then the user is not finished typing their password and display
    293   // the password suggestion.
    294   if (!element->isReadOnly() &&
    295       element->isEnabled() &&
    296       element->value().length() <= kMaximumOfferSize) {
    297     ShowGenerationPopup();
    298     return true;
    299   }
    300 
    301   return false;
    302 }
    303 
    304 bool PasswordGenerationAgent::TextDidChangeInTextField(
    305     const blink::WebInputElement& element) {
    306   if (element != generation_element_)
    307     return false;
    308 
    309   if (element.value().isEmpty()) {
    310     if (password_is_generated_) {
    311       // User generated a password and then deleted it.
    312       password_generation::LogPasswordGenerationEvent(
    313           password_generation::PASSWORD_DELETED);
    314     }
    315 
    316     // Do not treat the password as generated.
    317     // TODO(gcasto): Set PasswordForm::type in the browser to TYPE_NORMAL.
    318     password_is_generated_ = false;
    319     generation_element_.setShouldRevealPassword(false);
    320 
    321     // Offer generation again.
    322     ShowGenerationPopup();
    323   } else if (password_is_generated_) {
    324     password_edited_ = true;
    325     // Mirror edits to any confirmation password fields.
    326     for (std::vector<blink::WebInputElement>::iterator it =
    327              password_elements_.begin();
    328          it != password_elements_.end(); ++it) {
    329       it->setValue(element.value());
    330     }
    331   } else if (element.value().length() > kMaximumOfferSize) {
    332     // User has rejected the feature and has started typing a password.
    333     HidePopup();
    334   } else {
    335     // Password isn't generated and there are fewer than kMaximumOfferSize
    336     // characters typed, so keep offering the password. Note this function
    337     // will just keep the previous popup if one is already showing.
    338     ShowGenerationPopup();
    339   }
    340 
    341   return true;
    342 }
    343 
    344 void PasswordGenerationAgent::ShowGenerationPopup() {
    345   gfx::RectF bounding_box_scaled =
    346       GetScaledBoundingBox(render_view_->GetWebView()->pageScaleFactor(),
    347                            &generation_element_);
    348 
    349   Send(new AutofillHostMsg_ShowPasswordGenerationPopup(
    350       routing_id(),
    351       bounding_box_scaled,
    352       generation_element_.maxLength(),
    353       *possible_account_creation_form_));
    354 
    355   password_generation::LogPasswordGenerationEvent(
    356       password_generation::GENERATION_POPUP_SHOWN);
    357 }
    358 
    359 void PasswordGenerationAgent::ShowEditingPopup() {
    360   gfx::RectF bounding_box_scaled =
    361       GetScaledBoundingBox(render_view_->GetWebView()->pageScaleFactor(),
    362                            &generation_element_);
    363 
    364   Send(new AutofillHostMsg_ShowPasswordEditingPopup(
    365       routing_id(),
    366       bounding_box_scaled,
    367       *possible_account_creation_form_));
    368 
    369   password_generation::LogPasswordGenerationEvent(
    370       password_generation::EDITING_POPUP_SHOWN);
    371 }
    372 
    373 void PasswordGenerationAgent::HidePopup() {
    374   Send(new AutofillHostMsg_HidePasswordGenerationPopup(routing_id()));
    375 }
    376 
    377 }  // namespace autofill
    378