| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/password_manager/core/browser/new_password_form_manager.h" |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/stl_util.h" |
| #include "build/build_config.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/validation.h" |
| #include "components/autofill/core/common/password_form_generation_data.h" |
| #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h" |
| #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" |
| #include "components/password_manager/core/browser/form_fetcher_impl.h" |
| #include "components/password_manager/core/browser/form_saver.h" |
| #include "components/password_manager/core/browser/password_manager_client.h" |
| #include "components/password_manager/core/browser/password_manager_driver.h" |
| #include "components/password_manager/core/browser/password_manager_util.h" |
| |
| using autofill::FormData; |
| using autofill::FormFieldData; |
| using autofill::FormSignature; |
| using autofill::FormStructure; |
| using autofill::PasswordForm; |
| using autofill::ValueElementPair; |
| using base::TimeDelta; |
| using base::TimeTicks; |
| |
| using Logger = autofill::SavePasswordProgressLogger; |
| |
| namespace password_manager { |
| |
| bool NewPasswordFormManager::wait_for_server_predictions_for_filling_ = true; |
| |
| namespace { |
| |
| constexpr TimeDelta kMaxFillingDelayForServerPredictions = |
| TimeDelta::FromMilliseconds(500); |
| |
| // Helper to get the platform specific identifier by which autofill and password |
| // manager refer to a field. See http://crbug.com/896594 |
| base::string16 GetPlatformSpecificIdentifier(const FormFieldData& field) { |
| #if defined(OS_IOS) |
| return field.unique_id; |
| #else |
| return field.name; |
| #endif |
| } |
| |
| ValueElementPair PasswordToSave(const PasswordForm& form) { |
| if (form.new_password_value.empty()) { |
| DCHECK(!form.password_value.empty()); |
| return {form.password_value, form.password_element}; |
| } |
| return {form.new_password_value, form.new_password_element}; |
| } |
| |
| // Copies field properties masks from the form |from| to the form |to|. |
| void CopyFieldPropertiesMasks(const FormData& from, FormData* to) { |
| // Skip copying if the number of fields is different. |
| if (from.fields.size() != to->fields.size()) |
| return; |
| |
| for (size_t i = 0; i < from.fields.size(); ++i) { |
| to->fields[i].properties_mask = |
| GetPlatformSpecificIdentifier(to->fields[i]) == |
| GetPlatformSpecificIdentifier(from.fields[i]) |
| ? from.fields[i].properties_mask |
| : autofill::FieldPropertiesFlags::ERROR_OCCURRED; |
| } |
| } |
| |
| // Filter sensitive information, duplicates and |username_value| out from |
| // |form->other_possible_usernames|. |
| void SanitizePossibleUsernames(PasswordForm* form) { |
| auto& usernames = form->other_possible_usernames; |
| |
| // Deduplicate. |
| std::sort(usernames.begin(), usernames.end()); |
| usernames.erase(std::unique(usernames.begin(), usernames.end()), |
| usernames.end()); |
| |
| // Filter out |form->username_value| and sensitive information. |
| const base::string16& username_value = form->username_value; |
| base::EraseIf(usernames, [&username_value](const ValueElementPair& pair) { |
| return pair.first == username_value || |
| autofill::IsValidCreditCardNumber(pair.first) || |
| autofill::IsSSN(pair.first); |
| }); |
| } |
| |
| // Returns bit masks with differences in forms attributes which are important |
| // for parsing. Bits are set according to enum FormDataDifferences. |
| uint32_t FindFormsDifferences(const FormData& lhs, const FormData& rhs) { |
| if (lhs.fields.size() != rhs.fields.size()) |
| return PasswordFormMetricsRecorder::kFieldsNumber; |
| size_t differences_bitmask = 0; |
| for (size_t i = 0; i < lhs.fields.size(); ++i) { |
| const FormFieldData& lhs_field = lhs.fields[i]; |
| const FormFieldData& rhs_field = rhs.fields[i]; |
| |
| if (lhs_field.unique_renderer_id != rhs_field.unique_renderer_id) |
| differences_bitmask |= PasswordFormMetricsRecorder::kRendererFieldIDs; |
| |
| if (lhs_field.form_control_type != rhs_field.form_control_type) |
| differences_bitmask |= PasswordFormMetricsRecorder::kFormControlTypes; |
| |
| if (lhs_field.autocomplete_attribute != rhs_field.autocomplete_attribute) |
| differences_bitmask |= |
| PasswordFormMetricsRecorder::kAutocompleteAttributes; |
| } |
| return differences_bitmask; |
| } |
| |
| } // namespace |
| |
| NewPasswordFormManager::NewPasswordFormManager( |
| PasswordManagerClient* client, |
| const base::WeakPtr<PasswordManagerDriver>& driver, |
| const FormData& observed_form, |
| FormFetcher* form_fetcher, |
| std::unique_ptr<FormSaver> form_saver, |
| scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder) |
| : client_(client), |
| driver_(driver), |
| observed_form_(observed_form), |
| metrics_recorder_(metrics_recorder), |
| owned_form_fetcher_( |
| form_fetcher ? nullptr |
| : std::make_unique<FormFetcherImpl>( |
| PasswordStore::FormDigest(observed_form), |
| client_, |
| true /* should_migrate_http_passwords */, |
| true /* should_query_suppressed_https_forms */)), |
| form_fetcher_(form_fetcher ? form_fetcher : owned_form_fetcher_.get()), |
| form_saver_(std::move(form_saver)), |
| // TODO(https://crbug.com/831123): set correctly |
| // |is_possible_change_password_form| in |votes_uploader_| constructor |
| votes_uploader_(client, false /* is_possible_change_password_form */), |
| weak_ptr_factory_(this) { |
| if (!metrics_recorder_) { |
| metrics_recorder_ = base::MakeRefCounted<PasswordFormMetricsRecorder>( |
| client_->IsMainFrameSecure(), client_->GetUkmSourceId()); |
| } |
| metrics_recorder_->RecordFormSignature(CalculateFormSignature(observed_form)); |
| |
| if (owned_form_fetcher_) |
| owned_form_fetcher_->Fetch(); |
| form_fetcher_->AddConsumer(this); |
| |
| // The following code is for development and debugging purposes. |
| // TODO(https://crbug.com/831123): remove it when NewPasswordFormManager will |
| // be production ready. |
| if (password_manager_util::IsLoggingActive(client_)) |
| ParseFormAndMakeLogging(observed_form_, FormDataParser::Mode::kFilling); |
| } |
| NewPasswordFormManager::~NewPasswordFormManager() = default; |
| |
| bool NewPasswordFormManager::DoesManage( |
| const FormData& form, |
| const PasswordManagerDriver* driver) const { |
| if (driver != driver_.get()) |
| return false; |
| |
| if (observed_form_.is_form_tag != form.is_form_tag) |
| return false; |
| // All unowned input elements are considered as one synthetic form. |
| if (!observed_form_.is_form_tag && !form.is_form_tag) |
| return true; |
| #if defined(OS_IOS) |
| // On iOS form name is used as the form identifier. |
| return observed_form_.name == form.name; |
| #else |
| return observed_form_.unique_renderer_id == form.unique_renderer_id; |
| #endif |
| } |
| |
| bool NewPasswordFormManager::DoesManageAccordingToRendererId( |
| uint32_t form_renderer_id, |
| const PasswordManagerDriver* driver) const { |
| if (driver != driver_.get()) |
| return false; |
| #if defined(OS_IOS) |
| NOTREACHED(); |
| // On iOS form name is used as the form identifier. |
| return false; |
| #else |
| return observed_form_.unique_renderer_id == form_renderer_id; |
| #endif |
| } |
| |
| bool NewPasswordFormManager::IsEqualToSubmittedForm( |
| const autofill::FormData& form) const { |
| if (!is_submitted_) |
| return false; |
| if (form.action == submitted_form_.action) |
| return true; |
| // TODO(https://crbug.com/831123): Implement other checks from a function |
| // IsPasswordFormReappeared from password_manager.cc. |
| return false; |
| } |
| |
| FormFetcher* NewPasswordFormManager::GetFormFetcher() { |
| return form_fetcher_; |
| } |
| |
| const GURL& NewPasswordFormManager::GetOrigin() const { |
| return observed_form_.origin; |
| } |
| |
| const std::map<base::string16, const PasswordForm*>& |
| NewPasswordFormManager::GetBestMatches() const { |
| return best_matches_; |
| } |
| |
| const PasswordForm& NewPasswordFormManager::GetPendingCredentials() const { |
| return pending_credentials_; |
| } |
| |
| metrics_util::CredentialSourceType |
| NewPasswordFormManager::GetCredentialSource() { |
| return metrics_util::CredentialSourceType::kPasswordManager; |
| } |
| |
| PasswordFormMetricsRecorder* NewPasswordFormManager::GetMetricsRecorder() { |
| return metrics_recorder_.get(); |
| } |
| |
| const std::vector<const PasswordForm*>& |
| NewPasswordFormManager::GetBlacklistedMatches() const { |
| return blacklisted_matches_; |
| } |
| |
| bool NewPasswordFormManager::IsBlacklisted() const { |
| return !blacklisted_matches_.empty(); |
| } |
| |
| bool NewPasswordFormManager::IsPasswordOverridden() const { |
| return password_overridden_; |
| } |
| |
| const PasswordForm* NewPasswordFormManager::GetPreferredMatch() const { |
| return preferred_match_; |
| } |
| |
| void NewPasswordFormManager::Save() { |
| DCHECK_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); |
| DCHECK(!client_->IsIncognito()); |
| |
| // TODO(https://crbug.com/831123): Implement indicator event metrics. |
| if (password_overridden_ && |
| pending_credentials_.type == PasswordForm::TYPE_GENERATED && |
| !HasGeneratedPassword()) { |
| metrics_util::LogPasswordGenerationSubmissionEvent( |
| metrics_util::PASSWORD_OVERRIDDEN); |
| pending_credentials_.type = PasswordForm::TYPE_MANUAL; |
| } |
| |
| if (is_new_login_) { |
| SanitizePossibleUsernames(&pending_credentials_); |
| pending_credentials_.date_created = base::Time::Now(); |
| votes_uploader_.SendVotesOnSave(observed_form_, *parsed_submitted_form_, |
| best_matches_, &pending_credentials_); |
| form_saver_->Save(pending_credentials_, best_matches_); |
| } else { |
| ProcessUpdate(); |
| std::vector<PasswordForm> credentials_to_update = |
| FindOtherCredentialsToUpdate(); |
| form_saver_->Update(pending_credentials_, best_matches_, |
| &credentials_to_update, nullptr); |
| } |
| |
| if (pending_credentials_.times_used == 1 && |
| pending_credentials_.type == PasswordForm::TYPE_GENERATED) { |
| // This also includes PSL matched credentials. |
| metrics_util::LogPasswordGenerationSubmissionEvent( |
| metrics_util::PASSWORD_USED); |
| } |
| |
| client_->UpdateFormManagers(); |
| } |
| |
| void NewPasswordFormManager::Update(const PasswordForm& credentials_to_update) { |
| metrics_util::LogPasswordAcceptedSaveUpdateSubmissionIndicatorEvent( |
| parsed_submitted_form_->submission_event); |
| metrics_recorder_->SetSubmissionIndicatorEvent( |
| parsed_submitted_form_->submission_event); |
| |
| std::unique_ptr<PasswordForm> parsed_observed_form = |
| parser_.Parse(observed_form_, FormDataParser::Mode::kFilling); |
| |
| base::string16 password_to_save = pending_credentials_.password_value; |
| bool skip_zero_click = pending_credentials_.skip_zero_click; |
| pending_credentials_ = credentials_to_update; |
| pending_credentials_.password_value = password_to_save; |
| pending_credentials_.skip_zero_click = skip_zero_click; |
| pending_credentials_.preferred = true; |
| is_new_login_ = false; |
| ProcessUpdate(); |
| std::vector<PasswordForm> more_credentials_to_update = |
| FindOtherCredentialsToUpdate(); |
| form_saver_->Update(pending_credentials_, best_matches_, |
| &more_credentials_to_update, nullptr); |
| |
| client_->UpdateFormManagers(); |
| } |
| |
| void NewPasswordFormManager::UpdateUsername( |
| const base::string16& new_username) { |
| DCHECK(parsed_submitted_form_); |
| parsed_submitted_form_->username_value = new_username; |
| parsed_submitted_form_->username_element.clear(); |
| |
| // TODO(https://crbug.com/831123): Implement processing username editing votes |
| // after implementation of |other_possible_usernames|. |
| |
| CreatePendingCredentials(); |
| } |
| |
| void NewPasswordFormManager::UpdatePasswordValue( |
| const base::string16& new_password) { |
| DCHECK(parsed_submitted_form_); |
| parsed_submitted_form_->password_value = new_password; |
| parsed_submitted_form_->password_element.clear(); |
| |
| // TODO(https://crbug.com/831123): Implement processing password editing votes |
| // after implementation of |all_possible_passwords|. |
| CreatePendingCredentials(); |
| } |
| |
| // TODO(https://crbug.com/831123): Implement all methods from |
| // PasswordFormManagerForUI. |
| void NewPasswordFormManager::OnNopeUpdateClicked() {} |
| |
| void NewPasswordFormManager::OnNeverClicked() { |
| PermanentlyBlacklist(); |
| } |
| |
| void NewPasswordFormManager::OnNoInteraction(bool is_update) {} |
| |
| void NewPasswordFormManager::PermanentlyBlacklist() { |
| DCHECK(!client_->IsIncognito()); |
| |
| if (!new_blacklisted_) { |
| new_blacklisted_ = std::make_unique<PasswordForm>(); |
| new_blacklisted_->origin = observed_form_.origin; |
| new_blacklisted_->signon_realm = GetSignonRealm(observed_form_.origin); |
| blacklisted_matches_.push_back(new_blacklisted_.get()); |
| } |
| form_saver_->PermanentlyBlacklist(new_blacklisted_.get()); |
| } |
| |
| void NewPasswordFormManager::OnPasswordsRevealed() {} |
| |
| bool NewPasswordFormManager::IsNewLogin() const { |
| return is_new_login_; |
| } |
| |
| bool NewPasswordFormManager::IsPendingCredentialsPublicSuffixMatch() const { |
| return pending_credentials_.is_public_suffix_match; |
| } |
| |
| void NewPasswordFormManager::PresaveGeneratedPassword( |
| const PasswordForm& form) { |
| std::unique_ptr<PasswordForm> parsed_form = |
| ParseFormAndMakeLogging(form.form_data, FormDataParser::Mode::kSaving); |
| |
| if (!parsed_form) { |
| // Use the old parser result if parsing fails. |
| // TODO(https://crbug.com/831123). Make it work without the old parser. |
| parsed_form.reset(new PasswordForm(form)); |
| } |
| |
| if (!HasGeneratedPassword()) { |
| votes_uploader_.set_generated_password_changed(false); |
| metrics_recorder_->SetGeneratedPasswordStatus( |
| PasswordFormMetricsRecorder::GeneratedPasswordStatus:: |
| kPasswordAccepted); |
| } else { |
| // If the password is already generated and new value to presave differs |
| // from the presaved one, then mark that the generated password was changed. |
| // If a user recovers the original generated password, it will be recorded |
| // as a password change. |
| if (generated_password_ != form.password_value) { |
| votes_uploader_.set_generated_password_changed(true); |
| metrics_recorder_->SetGeneratedPasswordStatus( |
| PasswordFormMetricsRecorder::GeneratedPasswordStatus:: |
| kPasswordEdited); |
| } |
| } |
| votes_uploader_.set_has_generated_password(true); |
| |
| // TODO(https://crbug.com/831123). Propagate generated password independently |
| // of PasswordForm when PasswordForm goes away from the renderer process. |
| generated_password_ = form.password_value; |
| |
| // Set |password_value| to the generated password in order to ensure that the |
| // generated password is saved. |
| parsed_form->password_value = generated_password_; |
| |
| // Clear the username value if there are already saved credentials with |
| // the same username in order to prevent overwriting. |
| if (base::ContainsKey(best_matches_, parsed_form->username_value)) |
| parsed_form->username_value.clear(); |
| |
| form_saver_->PresaveGeneratedPassword(*parsed_form); |
| } |
| |
| void NewPasswordFormManager::PasswordNoLongerGenerated() { |
| DCHECK(HasGeneratedPassword()); |
| form_saver_->RemovePresavedPassword(); |
| generated_password_.clear(); |
| votes_uploader_.set_has_generated_password(false); |
| votes_uploader_.set_generated_password_changed(false); |
| metrics_recorder_->SetGeneratedPasswordStatus( |
| PasswordFormMetricsRecorder::GeneratedPasswordStatus::kPasswordDeleted); |
| } |
| |
| bool NewPasswordFormManager::HasGeneratedPassword() const { |
| return !generated_password_.empty(); |
| } |
| |
| void NewPasswordFormManager::SetGenerationPopupWasShown( |
| bool generation_popup_was_shown, |
| bool is_manual_generation) { |
| votes_uploader_.set_generation_popup_was_shown(generation_popup_was_shown); |
| votes_uploader_.set_is_manual_generation(is_manual_generation); |
| metrics_recorder_->SetPasswordGenerationPopupShown(generation_popup_was_shown, |
| is_manual_generation); |
| } |
| |
| void NewPasswordFormManager::SetGenerationElement( |
| const base::string16& generation_element) { |
| votes_uploader_.set_generation_element(generation_element); |
| } |
| |
| bool NewPasswordFormManager::IsPossibleChangePasswordFormWithoutUsername() |
| const { |
| return parsed_submitted_form_ && |
| parsed_submitted_form_->IsPossibleChangePasswordFormWithoutUsername(); |
| } |
| |
| bool NewPasswordFormManager::RetryPasswordFormPasswordUpdate() const { |
| return retry_password_form_password_update_; |
| } |
| |
| bool NewPasswordFormManager::IsPasswordUpdate() const { |
| return (!GetBestMatches().empty() && |
| IsPossibleChangePasswordFormWithoutUsername()) || |
| IsPasswordOverridden() || RetryPasswordFormPasswordUpdate(); |
| } |
| |
| std::vector<base::WeakPtr<PasswordManagerDriver>> |
| NewPasswordFormManager::GetDrivers() const { |
| return {driver_}; |
| } |
| |
| const PasswordForm* NewPasswordFormManager::GetSubmittedForm() const { |
| return parsed_submitted_form_.get(); |
| } |
| |
| std::unique_ptr<NewPasswordFormManager> NewPasswordFormManager::Clone() { |
| // Fetcher is cloned to avoid re-fetching data from PasswordStore. |
| std::unique_ptr<FormFetcher> fetcher = form_fetcher_->Clone(); |
| |
| // Some data is filled through the constructor. No PasswordManagerDriver is |
| // needed, because the UI does not need any functionality related to the |
| // renderer process, to which the driver serves as an interface. The full |
| // |observed_form_| needs to be copied, because it is used to create the |
| // blacklisting entry if needed. |
| auto result = std::make_unique<NewPasswordFormManager>( |
| client_, base::WeakPtr<PasswordManagerDriver>(), observed_form_, |
| fetcher.get(), form_saver_->Clone(), metrics_recorder_); |
| |
| // The constructor only can take a weak pointer to the fetcher, so moving the |
| // owning one needs to happen explicitly. |
| result->owned_form_fetcher_ = std::move(fetcher); |
| |
| // These data members all satisfy: |
| // (1) They could have been changed by |*this| between its construction and |
| // calling Clone(). |
| // (2) They are potentially used in the clone as the clone is used in the UI |
| // code. |
| // (3) They are not changed during ProcessMatches, triggered at some point |
| // by the cloned FormFetcher. |
| result->generated_password_ = generated_password_; |
| result->votes_uploader_ = votes_uploader_; |
| if (parser_.predictions()) |
| result->parser_.set_predictions(*parser_.predictions()); |
| |
| result->pending_credentials_ = pending_credentials_; |
| if (parsed_submitted_form_) { |
| result->parsed_submitted_form_.reset( |
| new PasswordForm(*parsed_submitted_form_)); |
| } |
| result->is_new_login_ = is_new_login_; |
| result->password_overridden_ = password_overridden_; |
| result->retry_password_form_password_update_ = |
| retry_password_form_password_update_; |
| result->is_submitted_ = is_submitted_; |
| |
| return result; |
| } |
| |
| void NewPasswordFormManager::ProcessMatches( |
| const std::vector<const PasswordForm*>& non_federated, |
| size_t filtered_count) { |
| received_stored_credentials_time_ = TimeTicks::Now(); |
| std::vector<const PasswordForm*> matches; |
| std::copy_if(non_federated.begin(), non_federated.end(), |
| std::back_inserter(matches), [](const PasswordForm* form) { |
| return !form->blacklisted_by_user && |
| form->scheme == PasswordForm::SCHEME_HTML; |
| }); |
| |
| password_manager_util::FindBestMatches(matches, &best_matches_, |
| ¬_best_matches_, &preferred_match_); |
| |
| // Copy out blacklisted matches. |
| blacklisted_matches_.clear(); |
| new_blacklisted_.reset(); |
| std::copy_if( |
| non_federated.begin(), non_federated.end(), |
| std::back_inserter(blacklisted_matches_), [](const PasswordForm* form) { |
| return form->blacklisted_by_user && !form->is_public_suffix_match; |
| }); |
| |
| autofills_left_ = kMaxTimesAutofill; |
| |
| if (parser_.predictions() || !wait_for_server_predictions_for_filling_) { |
| ReportTimeBetweenStoreAndServerUMA(); |
| Fill(); |
| } else if (!waiting_for_server_predictions_) { |
| waiting_for_server_predictions_ = true; |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&NewPasswordFormManager::Fill, |
| weak_ptr_factory_.GetWeakPtr()), |
| kMaxFillingDelayForServerPredictions); |
| } |
| } |
| |
| bool NewPasswordFormManager::ProvisionallySaveIfIsManaged( |
| const autofill::FormData& submitted_form, |
| const PasswordManagerDriver* driver) { |
| if (!DoesManage(submitted_form, driver)) |
| return false; |
| |
| std::unique_ptr<PasswordForm> parsed_submitted_form = |
| ParseFormAndMakeLogging(submitted_form, FormDataParser::Mode::kSaving); |
| |
| RecordMetricOnReadonly(parser_.readonly_status(), !!parsed_submitted_form, |
| FormDataParser::Mode::kSaving); |
| |
| // This function might be called multiple times. Consider as success if the |
| // submitted form was successfully parsed on a previous call. |
| if (!parsed_submitted_form) |
| return is_submitted_; |
| |
| parsed_submitted_form_ = std::move(parsed_submitted_form); |
| submitted_form_ = submitted_form; |
| is_submitted_ = true; |
| CalculateFillingAssistanceMetric(submitted_form); |
| |
| CreatePendingCredentials(); |
| return true; |
| } |
| |
| void NewPasswordFormManager::ProcessServerPredictions( |
| const std::map<FormSignature, FormPredictions>& predictions) { |
| FormSignature observed_form_signature = |
| CalculateFormSignature(observed_form_); |
| auto it = predictions.find(observed_form_signature); |
| if (it == predictions.end()) |
| return; |
| |
| ReportTimeBetweenStoreAndServerUMA(); |
| parser_.set_predictions(it->second); |
| Fill(); |
| } |
| |
| void NewPasswordFormManager::Fill() { |
| if (!driver_) |
| return; |
| |
| waiting_for_server_predictions_ = false; |
| |
| if (form_fetcher_->GetState() == FormFetcher::State::WAITING) |
| return; |
| |
| if (autofills_left_ <= 0) |
| return; |
| autofills_left_--; |
| |
| // There are additional signals (server-side data) and parse results in |
| // filling and saving mode might be different so it is better not to cache |
| // parse result, but to parse each time again. |
| std::unique_ptr<PasswordForm> observed_password_form = |
| ParseFormAndMakeLogging(observed_form_, FormDataParser::Mode::kFilling); |
| RecordMetricOnReadonly(parser_.readonly_status(), !!observed_password_form, |
| FormDataParser::Mode::kFilling); |
| if (!observed_password_form) |
| return; |
| |
| RecordMetricOnCompareParsingResult(*observed_password_form); |
| |
| if (observed_password_form->is_new_password_reliable && !IsBlacklisted()) { |
| #if defined(OS_IOS) |
| driver_->FormEligibleForGenerationFound( |
| {.form_name = observed_password_form->form_data.name, |
| .new_password_element = observed_password_form->new_password_element, |
| .confirmation_password_element = |
| observed_password_form->confirmation_password_element}); |
| #else |
| driver_->FormEligibleForGenerationFound( |
| {.new_password_renderer_id = |
| observed_password_form->new_password_element_renderer_id, |
| .confirmation_password_renderer_id = |
| observed_password_form |
| ->confirmation_password_element_renderer_id}); |
| #endif |
| } |
| |
| // TODO(https://crbug.com/831123). Implement correct treating of federated |
| // matches. |
| std::vector<const PasswordForm*> federated_matches; |
| likely_form_filling_ = SendFillInformationToRenderer( |
| *client_, driver_.get(), IsBlacklisted(), *observed_password_form.get(), |
| best_matches_, federated_matches, preferred_match_, |
| metrics_recorder_.get()); |
| } |
| |
| void NewPasswordFormManager::FillForm(const FormData& observed_form) { |
| uint32_t differences_bitmask = |
| FindFormsDifferences(observed_form_, observed_form); |
| metrics_recorder_->RecordFormChangeBitmask(differences_bitmask); |
| |
| if (differences_bitmask) |
| observed_form_ = observed_form; |
| |
| if (!waiting_for_server_predictions_) |
| Fill(); |
| } |
| |
| void NewPasswordFormManager::RecordMetricOnCompareParsingResult( |
| const PasswordForm& parsed_form) { |
| bool same = |
| parsed_form.username_element == old_parsing_result_.username_element && |
| parsed_form.password_element == old_parsing_result_.password_element && |
| parsed_form.new_password_element == |
| old_parsing_result_.new_password_element && |
| parsed_form.confirmation_password_element == |
| old_parsing_result_.confirmation_password_element; |
| if (same) { |
| metrics_recorder_->RecordParsingsComparisonResult( |
| PasswordFormMetricsRecorder::ParsingComparisonResult::kSame); |
| return; |
| } |
| |
| // In the old parsing for fields with empty name, placeholders are used. The |
| // reason for this is that an empty "..._element" attribute in a PasswordForm |
| // means that no corresponding input element exists. The new form parsing sets |
| // empty string in that case because renderer ids are used instead of element |
| // names for fields identification. Hence in case of anonymous fields, the |
| // results will be different for sure. Compare to placeholders and record this |
| // case. |
| if (old_parsing_result_.username_element == |
| base::ASCIIToUTF16("anonymous_username") || |
| old_parsing_result_.password_element == |
| base::ASCIIToUTF16("anonymous_password") || |
| old_parsing_result_.new_password_element == |
| base::ASCIIToUTF16("anonymous_new_password") || |
| old_parsing_result_.confirmation_password_element == |
| base::ASCIIToUTF16("anonymous_confirmation_password")) { |
| metrics_recorder_->RecordParsingsComparisonResult( |
| PasswordFormMetricsRecorder::ParsingComparisonResult::kAnonymousFields); |
| } else { |
| metrics_recorder_->RecordParsingsComparisonResult( |
| PasswordFormMetricsRecorder::ParsingComparisonResult::kDifferent); |
| } |
| } |
| |
| void NewPasswordFormManager::RecordMetricOnReadonly( |
| FormDataParser::ReadonlyPasswordFields readonly_status, |
| bool parsing_successful, |
| FormDataParser::Mode mode) { |
| // The reported value is combined of the |readonly_status| shifted by one bit |
| // to the left, and the success bit put in the least significant bit. Note: |
| // C++ guarantees that bool->int conversions map false to 0 and true to 1. |
| uint64_t value = static_cast<uint64_t>(parsing_successful) + |
| (static_cast<uint64_t>(readonly_status) << 1); |
| switch (mode) { |
| case FormDataParser::Mode::kSaving: |
| metrics_recorder_->RecordReadonlyWhenSaving(value); |
| break; |
| case FormDataParser::Mode::kFilling: |
| metrics_recorder_->RecordReadonlyWhenFilling(value); |
| break; |
| } |
| } |
| |
| void NewPasswordFormManager::ReportTimeBetweenStoreAndServerUMA() { |
| if (!received_stored_credentials_time_.is_null()) { |
| UMA_HISTOGRAM_TIMES("PasswordManager.TimeBetweenStoreAndServer", |
| TimeTicks::Now() - received_stored_credentials_time_); |
| } |
| } |
| |
| void NewPasswordFormManager::CreatePendingCredentials() { |
| DCHECK(is_submitted_); |
| // TODO(https://crbug.com/831123): Process correctly the case when saved |
| // credentials are not received from the store yet. |
| if (!parsed_submitted_form_) |
| return; |
| |
| // This function might be called multiple times so set variables that are |
| // changed in this function to initial states. |
| is_new_login_ = true; |
| SetPasswordOverridden(false); |
| retry_password_form_password_update_ = false; |
| |
| ValueElementPair password_to_save(PasswordToSave(*parsed_submitted_form_)); |
| |
| // Look for the actually submitted credentials in the list of previously saved |
| // credentials that were available to autofilling. |
| const PasswordForm* saved_form = |
| FindBestSavedMatch(parsed_submitted_form_.get()); |
| if (saved_form) { |
| // The user signed in with a login we autofilled. |
| pending_credentials_ = *saved_form; |
| SetPasswordOverridden(pending_credentials_.password_value != |
| password_to_save.first); |
| |
| if (pending_credentials_.is_public_suffix_match) { |
| // If the autofilled credentials were a PSL match or credentials stored |
| // from Android apps, store a copy with the current origin and signon |
| // realm. This ensures that on the next visit, a precise match is found. |
| is_new_login_ = true; |
| metrics_recorder_->SetUserAction(password_overridden_ |
| ? UserAction::kOverridePassword |
| : UserAction::kChoosePslMatch); |
| |
| // Update credential to reflect that it has been used for submission. |
| // If this isn't updated, then password generation uploads are off for |
| // sites where PSL matching is required to fill the login form, as two |
| // PASSWORD votes are uploaded per saved password instead of one. |
| password_manager_util::UpdateMetadataForUsage(&pending_credentials_); |
| |
| // Update |pending_credentials_| in order to be able correctly save it. |
| pending_credentials_.origin = submitted_form_.origin; |
| pending_credentials_.signon_realm = parsed_submitted_form_->signon_realm; |
| |
| // Normally, the copy of the PSL matched credentials, adapted for the |
| // current domain, is saved automatically without asking the user, because |
| // the copy likely represents the same account, i.e., the one for which |
| // the user already agreed to store a password. |
| // |
| // However, if the user changes the suggested password, it might indicate |
| // that the autofilled credentials and |submitted_password_form| |
| // actually correspond to two different accounts (see |
| // http://crbug.com/385619). In that case the user should be asked again |
| // before saving the password. This is ensured by setting |
| // |password_overriden_| on |pending_credentials_| to false. |
| // |
| // There is still the edge case when the autofilled credentials represent |
| // the same account as |submitted_password_form| but the stored password |
| // was out of date. In that case, the user just had to manually enter the |
| // new password, which is now in |submitted_password_form|. The best |
| // thing would be to save automatically, and also update the original |
| // credentials. However, we have no way to tell if this is the case. |
| // This will likely happen infrequently, and the inconvenience put on the |
| // user by asking them is not significant, so we are fine with asking |
| // here again. |
| if (password_overridden_) { |
| pending_credentials_.is_public_suffix_match = false; |
| SetPasswordOverridden(false); |
| } |
| } else { // Not a PSL match but a match of an already stored credential. |
| is_new_login_ = false; |
| if (password_overridden_) { |
| metrics_recorder_->SetUserAction(UserAction::kOverridePassword); |
| } else { |
| // In case |saved_form| is pointing to the same form as |
| // |preferred_match_| and the form was filled on page load, the user |
| // either did not do anything, or re-selected the default option. |
| // Otherwise, the user purposefully chose a credential. |
| metrics_recorder_->SetUserAction( |
| saved_form == preferred_match_ && |
| likely_form_filling_ == LikelyFormFilling::kFillOnPageLoad |
| ? UserAction::kNone |
| : UserAction::kChoose); |
| } |
| } |
| } else if (!best_matches_.empty() && |
| parsed_submitted_form_->type != PasswordForm::TYPE_API && |
| parsed_submitted_form_->username_value.empty()) { |
| // This branch deals with the case that the submitted form has no username |
| // element and needs to decide whether to offer to update any credentials. |
| // In that case, the user can select any previously stored credential as |
| // the one to update, but we still try to find the best candidate. |
| |
| // Find the best candidate to select by default in the password update |
| // bubble. If no best candidate is found, any one can be offered. |
| const PasswordForm* best_update_match = |
| FindBestMatchForUpdatePassword(parsed_submitted_form_->password_value); |
| |
| // A retry password form is one that consists of only an "old password" |
| // field, i.e. one that is not a "new password". |
| retry_password_form_password_update_ = |
| parsed_submitted_form_->username_value.empty() && |
| parsed_submitted_form_->new_password_value.empty(); |
| |
| is_new_login_ = false; |
| if (best_update_match) { |
| // Chose |best_update_match| to be updated. |
| pending_credentials_ = *best_update_match; |
| } else if (HasGeneratedPassword()) { |
| // If a password was generated and we didn't find a match, we have to save |
| // it in a separate entry since we have to store it but we don't know |
| // where. |
| CreatePendingCredentialsForNewCredentials(*parsed_submitted_form_, |
| password_to_save.second); |
| is_new_login_ = true; |
| } else { |
| // We don't have a good candidate to choose as the default credential for |
| // the update bubble and the user has to pick one. |
| // We set |pending_credentials_| to the bare minimum, which is the correct |
| // origin. |
| pending_credentials_.origin = submitted_form_.origin; |
| } |
| } else { |
| is_new_login_ = true; |
| // No stored credentials can be matched to the submitted form. Offer to |
| // save new credentials. |
| CreatePendingCredentialsForNewCredentials(*parsed_submitted_form_, |
| password_to_save.second); |
| // Generate username correction votes. |
| bool username_correction_found = |
| votes_uploader_.FindCorrectedUsernameElement( |
| best_matches_, not_best_matches_, |
| parsed_submitted_form_->username_value, |
| parsed_submitted_form_->password_value); |
| UMA_HISTOGRAM_BOOLEAN("PasswordManager.UsernameCorrectionFound", |
| username_correction_found); |
| if (username_correction_found) { |
| metrics_recorder_->RecordDetailedUserAction( |
| password_manager::PasswordFormMetricsRecorder::DetailedUserAction:: |
| kCorrectedUsernameInForm); |
| } |
| } |
| |
| if (!IsValidAndroidFacetURI(pending_credentials_.signon_realm)) |
| pending_credentials_.action = submitted_form_.action; |
| |
| pending_credentials_.password_value = generated_password_.empty() |
| ? password_to_save.first |
| : generated_password_; |
| pending_credentials_.preferred = true; |
| pending_credentials_.form_has_autofilled_value = |
| parsed_submitted_form_->form_has_autofilled_value; |
| pending_credentials_.all_possible_passwords = |
| parsed_submitted_form_->all_possible_passwords; |
| CopyFieldPropertiesMasks(submitted_form_, &pending_credentials_.form_data); |
| |
| // If we're dealing with an API-driven provisionally saved form, then take |
| // the server provided values. We don't do this for non-API forms, as |
| // those will never have those members set. |
| if (parsed_submitted_form_->type == PasswordForm::TYPE_API) { |
| pending_credentials_.skip_zero_click = |
| parsed_submitted_form_->skip_zero_click; |
| pending_credentials_.display_name = parsed_submitted_form_->display_name; |
| pending_credentials_.federation_origin = |
| parsed_submitted_form_->federation_origin; |
| pending_credentials_.icon_url = parsed_submitted_form_->icon_url; |
| // It's important to override |signon_realm| for federated credentials |
| // because it has format "federation://" + origin_host + "/" + |
| // federation_host |
| pending_credentials_.signon_realm = parsed_submitted_form_->signon_realm; |
| } |
| |
| if (HasGeneratedPassword()) |
| pending_credentials_.type = PasswordForm::TYPE_GENERATED; |
| } |
| |
| const PasswordForm* NewPasswordFormManager::FindBestMatchForUpdatePassword( |
| const base::string16& password) const { |
| // This function is called for forms that do not contain a username field. |
| // This means that we cannot update credentials based on a matching username |
| // and that we may need to show an update prompt. |
| if (best_matches_.size() == 1 && !HasGeneratedPassword()) { |
| // In case the submitted form contained no username but a password, and if |
| // the user has only one credential stored, return it as the one that should |
| // be updated. |
| return best_matches_.begin()->second; |
| } |
| if (password.empty()) |
| return nullptr; |
| |
| // Return any existing credential that has the same |password| saved already. |
| for (const auto& key_value : best_matches_) { |
| if (key_value.second->password_value == password) |
| return key_value.second; |
| } |
| return nullptr; |
| } |
| |
| const PasswordForm* NewPasswordFormManager::FindBestSavedMatch( |
| const PasswordForm* submitted_form) const { |
| if (!submitted_form->federation_origin.opaque()) |
| return nullptr; |
| |
| // Return form with matching |username_value|. |
| auto it = best_matches_.find(submitted_form->username_value); |
| if (it != best_matches_.end()) |
| return it->second; |
| |
| // Match Credential API forms only by username. Stop here if nothing was found |
| // above. |
| if (submitted_form->type == PasswordForm::TYPE_API) |
| return nullptr; |
| |
| // Verify that the submitted form has no username and no "new password" |
| // and bail out with a nullptr otherwise. |
| bool submitted_form_has_username = !submitted_form->username_value.empty(); |
| bool submitted_form_has_new_password_element = |
| !submitted_form->new_password_value.empty(); |
| if (submitted_form_has_username || submitted_form_has_new_password_element) |
| return nullptr; |
| |
| // At this line we are certain that the submitted form contains only a |
| // password field that is not a "new password". Now we can check whether we |
| // have a match by password of an already saved credential. |
| for (const auto& stored_match : best_matches_) { |
| if (stored_match.second->password_value == submitted_form->password_value) |
| return stored_match.second; |
| } |
| return nullptr; |
| } |
| |
| void NewPasswordFormManager::CreatePendingCredentialsForNewCredentials( |
| const PasswordForm& submitted_password_form, |
| const base::string16& password_element) { |
| // User typed in a new, unknown username. |
| metrics_recorder_->SetUserAction(UserAction::kOverrideUsernameAndPassword); |
| // TODO(https://crbug.com/831123): Replace parsing of the observed form with |
| // usage of already parsed submitted form. |
| std::unique_ptr<PasswordForm> parsed_observed_form = |
| ParseFormAndMakeLogging(observed_form_, FormDataParser::Mode::kFilling); |
| if (!parsed_observed_form) |
| return; |
| pending_credentials_ = *parsed_observed_form; |
| pending_credentials_.username_element = |
| submitted_password_form.username_element; |
| pending_credentials_.username_value = submitted_password_form.username_value; |
| pending_credentials_.other_possible_usernames = |
| submitted_password_form.other_possible_usernames; |
| pending_credentials_.all_possible_passwords = |
| submitted_password_form.all_possible_passwords; |
| |
| // The password value will be filled in later, remove any garbage for now. |
| pending_credentials_.password_value.clear(); |
| // The password element should be determined earlier in |PasswordToSave|. |
| pending_credentials_.password_element = password_element; |
| // The new password's value and element name should be empty. |
| pending_credentials_.new_password_value.clear(); |
| pending_credentials_.new_password_element.clear(); |
| } |
| |
| void NewPasswordFormManager::ProcessUpdate() { |
| DCHECK_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); |
| DCHECK(preferred_match_ || !pending_credentials_.federation_origin.opaque()); |
| // If we're doing an Update, we either autofilled correctly and need to |
| // update the stats, or the user typed in a new password for autofilled |
| // username, or the user selected one of the non-preferred matches, |
| // thus requiring a swap of preferred bits. |
| DCHECK(!is_new_login_ && pending_credentials_.preferred); |
| DCHECK(!client_->IsIncognito()); |
| DCHECK(parsed_submitted_form_); |
| |
| password_manager_util::UpdateMetadataForUsage(&pending_credentials_); |
| |
| base::RecordAction( |
| base::UserMetricsAction("PasswordManager_LoginFollowingAutofill")); |
| |
| // Check to see if this form is a candidate for password generation. |
| // Do not send votes on change password forms, since they were already sent in |
| // Update() method. |
| if (!parsed_submitted_form_->IsPossibleChangePasswordForm()) { |
| votes_uploader_.SendVoteOnCredentialsReuse( |
| observed_form_, *parsed_submitted_form_, &pending_credentials_); |
| } |
| if (IsPasswordUpdate()) { |
| votes_uploader_.UploadPasswordVote( |
| *parsed_submitted_form_, *parsed_submitted_form_, |
| autofill::NEW_PASSWORD, |
| autofill::FormStructure(pending_credentials_.form_data) |
| .FormSignatureAsStr()); |
| } |
| |
| if (pending_credentials_.times_used == 1) { |
| votes_uploader_.UploadFirstLoginVotes(best_matches_, pending_credentials_, |
| *parsed_submitted_form_); |
| } |
| } |
| |
| std::vector<PasswordForm> |
| NewPasswordFormManager::FindOtherCredentialsToUpdate() { |
| std::vector<autofill::PasswordForm> credentials_to_update; |
| if (!pending_credentials_.federation_origin.opaque()) |
| return credentials_to_update; |
| |
| auto updated_password_it = |
| best_matches_.find(pending_credentials_.username_value); |
| DCHECK(best_matches_.end() != updated_password_it); |
| const base::string16& old_password = |
| updated_password_it->second->password_value; |
| for (auto* not_best_match : not_best_matches_) { |
| if (not_best_match->username_value == pending_credentials_.username_value && |
| not_best_match->password_value == old_password) { |
| credentials_to_update.push_back(*not_best_match); |
| credentials_to_update.back().password_value = |
| pending_credentials_.password_value; |
| } |
| } |
| |
| return credentials_to_update; |
| } |
| |
| std::unique_ptr<PasswordForm> NewPasswordFormManager::ParseFormAndMakeLogging( |
| const FormData& form, |
| FormDataParser::Mode mode) { |
| std::unique_ptr<PasswordForm> password_form = parser_.Parse(form, mode); |
| |
| if (password_manager_util::IsLoggingActive(client_)) { |
| BrowserSavePasswordProgressLogger logger(client_->GetLogManager()); |
| logger.LogFormData(Logger::STRING_FORM_PARSING_INPUT, form); |
| if (password_form) |
| logger.LogPasswordForm(Logger::STRING_FORM_PARSING_OUTPUT, |
| *password_form); |
| } |
| return password_form; |
| } |
| |
| void NewPasswordFormManager::CalculateFillingAssistanceMetric( |
| const FormData& submitted_form) { |
| // TODO(https://crbug.com/918846): implement collecting all necessary data on |
| // iOS. |
| #if not defined(OS_IOS) |
| std::set<base::string16> saved_usernames; |
| std::set<base::string16> saved_passwords; |
| |
| const std::vector<const PasswordForm*>& saved_forms = |
| form_fetcher_->GetNonFederatedMatches(); |
| |
| for (auto* saved_form : saved_forms) { |
| saved_usernames.insert(saved_form->username_value); |
| saved_passwords.insert(saved_form->password_value); |
| } |
| |
| // Saved credentials might have empty usernames which are not interesting for |
| // filling assistance metric. |
| saved_usernames.erase(base::string16()); |
| |
| metrics_recorder_->CalculateFillingAssistanceMetric( |
| submitted_form, saved_usernames, saved_passwords, |
| form_fetcher_->GetInteractionsStats()); |
| #endif |
| } |
| |
| } // namespace password_manager |