| // Copyright (c) 2012 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/password_manager.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/platform_thread.h" |
| #include "build/build_config.h" |
| #include "components/autofill/core/browser/autofill_field.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/logging/log_manager.h" |
| #include "components/autofill/core/common/form_data_predictions.h" |
| #include "components/autofill/core/common/password_form_field_prediction_map.h" |
| #include "components/autofill/core/common/save_password_progress_logger.h" |
| #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" |
| #include "components/password_manager/core/browser/form_saver_impl.h" |
| #include "components/password_manager/core/browser/password_autofill_manager.h" |
| #include "components/password_manager/core/browser/password_form_manager.h" |
| #include "components/password_manager/core/browser/password_generation_frame_helper.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_metrics_util.h" |
| #include "components/password_manager/core/browser/password_manager_onboarding.h" |
| #include "components/password_manager/core/browser/password_manager_util.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "components/password_manager/core/common/password_manager_pref_names.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| |
| #if defined(OS_WIN) |
| #include "components/prefs/pref_registry_simple.h" |
| #endif |
| |
| using autofill::FormData; |
| using autofill::FormStructure; |
| using autofill::PasswordForm; |
| using autofill::mojom::PasswordFormFieldPredictionType; |
| #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) |
| using password_manager::metrics_util::GaiaPasswordHashChange; |
| #endif // SYNC_PASSWORD_REUSE_DETECTION_ENABLED |
| |
| namespace password_manager { |
| |
| namespace { |
| |
| // Shorten the name to spare line breaks. The code provides enough context |
| // already. |
| using Logger = autofill::SavePasswordProgressLogger; |
| |
| bool URLsEqualUpToScheme(const GURL& a, const GURL& b) { |
| return (a.GetContent() == b.GetContent()); |
| } |
| |
| bool URLsEqualUpToHttpHttpsSubstitution(const GURL& a, const GURL& b) { |
| if (a == b) |
| return true; |
| |
| // The first-time and retry login forms action URLs sometimes differ in |
| // switching from HTTP to HTTPS, see http://crbug.com/400769. |
| if (a.SchemeIsHTTPOrHTTPS() && b.SchemeIsHTTPOrHTTPS()) |
| return URLsEqualUpToScheme(a, b); |
| |
| return false; |
| } |
| |
| // Since empty or unspecified form's action is automatically set to the page |
| // origin, this function checks if a form's action is empty by comparing it to |
| // its origin. |
| bool HasNonEmptyAction(const PasswordForm& form) { |
| return form.action != form.origin; |
| } |
| |
| // Checks if the observed form looks like the submitted one to handle "Invalid |
| // password entered" case so we don't offer a password save when we shouldn't. |
| bool IsPasswordFormReappeared(const PasswordForm& observed_form, |
| const PasswordForm& submitted_form) { |
| if (observed_form.action.is_valid() && HasNonEmptyAction(observed_form) && |
| HasNonEmptyAction(submitted_form) && |
| URLsEqualUpToHttpHttpsSubstitution(submitted_form.action, |
| observed_form.action)) { |
| return true; |
| } |
| |
| // Match the form if username and password fields are same. |
| if (base::EqualsCaseInsensitiveASCII(observed_form.username_element, |
| submitted_form.username_element) && |
| base::EqualsCaseInsensitiveASCII(observed_form.password_element, |
| submitted_form.password_element)) { |
| return true; |
| } |
| |
| // Match the form if the observed username field has the same value as in |
| // the submitted form. |
| if (!submitted_form.username_value.empty() && |
| observed_form.username_value == submitted_form.username_value) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool AreAllFieldsEmpty(const PasswordForm& form) { |
| return form.username_value.empty() && form.password_value.empty() && |
| form.new_password_value.empty(); |
| } |
| |
| // Returns true if the user needs to be prompted before a password can be |
| // saved (instead of automatically saving the password), based on inspecting |
| // the state of |manager|. |
| bool ShouldPromptUserToSavePassword( |
| const PasswordFormManagerInterface& manager) { |
| if (manager.IsPasswordUpdate()) { |
| // Updating a credential might erase a useful stored value by accident. |
| // Always ask the user to confirm. |
| return true; |
| } |
| |
| // User successfully signed-in with PSL match credentials. These credentials |
| // should be automatically saved in order to be autofilled on next login. |
| if (manager.IsPendingCredentialsPublicSuffixMatch()) |
| return false; |
| |
| if (manager.HasGeneratedPassword()) |
| return false; |
| |
| return manager.IsNewLogin(); |
| } |
| |
| // Checks that |form| has visible password fields. It should be used only for |
| // GAIA forms. |
| bool IsThereVisiblePasswordField(const FormData& form) { |
| for (const autofill::FormFieldData& field : form.fields) { |
| if (field.form_control_type == "password" && field.is_focusable) |
| return true; |
| } |
| return false; |
| } |
| |
| // Finds the matched form manager for |form| in |form_managers|. |
| PasswordFormManager* FindMatchedManager( |
| const FormData& form, |
| const std::vector<std::unique_ptr<PasswordFormManager>>& form_managers, |
| const PasswordManagerDriver* driver) { |
| for (const auto& form_manager : form_managers) { |
| if (form_manager->DoesManage(form, driver)) |
| return form_manager.get(); |
| } |
| return nullptr; |
| } |
| |
| // Finds the matched form manager with id |form_renderer_id| in |form_managers|. |
| PasswordFormManager* FindMatchedManagerByRendererId( |
| uint32_t form_renderer_id, |
| const std::vector<std::unique_ptr<PasswordFormManager>>& form_managers, |
| const PasswordManagerDriver* driver) { |
| for (const auto& form_manager : form_managers) { |
| if (form_manager->DoesManageAccordingToRendererId(form_renderer_id, driver)) |
| return form_manager.get(); |
| } |
| return nullptr; |
| } |
| |
| bool HasSingleUsernameVote(const FormStructure& form) { |
| for (const auto& field : form) { |
| if (field->server_type() == autofill::SINGLE_USERNAME) |
| return true; |
| } |
| return false; |
| } |
| |
| // Returns true if at least one of the fields in |form| has a prediction to be a |
| // new-password related field. |
| // corresponds to a field for creating or changing a password. |
| bool HasNewPasswordVote(const FormStructure& form) { |
| for (const auto& field : form) { |
| if (field->server_type() == autofill::ACCOUNT_CREATION_PASSWORD || |
| field->server_type() == autofill::NEW_PASSWORD) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // static |
| void PasswordManager::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref(prefs::kBlacklistedCredentialsNormalized, |
| false); |
| registry->RegisterBooleanPref( |
| prefs::kCredentialsEnableService, true, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); |
| registry->RegisterBooleanPref( |
| prefs::kCredentialsEnableAutosignin, true, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); |
| registry->RegisterStringPref(prefs::kSyncPasswordHash, std::string(), |
| PrefRegistry::NO_REGISTRATION_FLAGS); |
| registry->RegisterStringPref(prefs::kSyncPasswordLengthAndHashSalt, |
| std::string(), |
| PrefRegistry::NO_REGISTRATION_FLAGS); |
| registry->RegisterBooleanPref( |
| prefs::kWasAutoSignInFirstRunExperienceShown, false, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF); |
| registry->RegisterDoublePref(prefs::kLastTimeObsoleteHttpCredentialsRemoved, |
| 0.0); |
| registry->RegisterIntegerPref( |
| prefs::kPasswordManagerOnboardingState, |
| static_cast<int>(metrics_util::OnboardingState::kDoNotShow)); |
| |
| #if defined(OS_MACOSX) |
| registry->RegisterIntegerPref(prefs::kKeychainMigrationStatus, |
| 4 /* MIGRATED_DELETED */); |
| #endif |
| registry->RegisterListPref(prefs::kPasswordHashDataList, |
| PrefRegistry::NO_REGISTRATION_FLAGS); |
| registry->RegisterBooleanPref( |
| prefs::kPasswordLeakDetectionEnabled, true, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| } |
| |
| // static |
| void PasswordManager::RegisterLocalPrefs(PrefRegistrySimple* registry) { |
| #if defined(OS_WIN) |
| registry->RegisterInt64Pref(prefs::kOsPasswordLastChanged, 0); |
| registry->RegisterBooleanPref(prefs::kOsPasswordBlank, false); |
| #endif |
| |
| #if defined(OS_MACOSX) && !defined(OS_IOS) |
| registry->RegisterTimePref(prefs::kPasswordRecovery, base::Time()); |
| #endif |
| } |
| |
| PasswordManager::PasswordManager(PasswordManagerClient* client) |
| : client_(client) |
| #if !defined(OS_IOS) |
| , |
| leak_delegate_(client) |
| #endif // !defined(OS_IOS) |
| { |
| DCHECK(client_); |
| } |
| |
| PasswordManager::~PasswordManager() = default; |
| |
| void PasswordManager::OnGeneratedPasswordAccepted( |
| PasswordManagerDriver* driver, |
| const FormData& form_data, |
| uint32_t generation_element_id, |
| const base::string16& password) { |
| PasswordFormManager* manager = GetMatchedManager(driver, form_data); |
| if (manager) { |
| manager->OnGeneratedPasswordAccepted(form_data, generation_element_id, |
| password); |
| } else { |
| // OnPresaveGeneratedPassword records the histogram in all other cases. |
| UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager", |
| true); |
| } |
| } |
| |
| void PasswordManager::OnPresaveGeneratedPassword(PasswordManagerDriver* driver, |
| const PasswordForm& form) { |
| DCHECK(client_->IsSavingAndFillingEnabled(form.origin)); |
| PasswordFormManagerInterface* form_manager = GetMatchedManager(driver, form); |
| UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager", |
| !form_manager); |
| if (form_manager) |
| form_manager->PresaveGeneratedPassword(form); |
| } |
| |
| void PasswordManager::OnPasswordNoLongerGenerated(PasswordManagerDriver* driver, |
| const PasswordForm& form) { |
| DCHECK(client_->IsSavingAndFillingEnabled(form.origin)); |
| |
| PasswordFormManagerInterface* form_manager = GetMatchedManager(driver, form); |
| if (form_manager) |
| form_manager->PasswordNoLongerGenerated(); |
| } |
| |
| void PasswordManager::SetGenerationElementAndReasonForForm( |
| password_manager::PasswordManagerDriver* driver, |
| const PasswordForm& form, |
| const base::string16& generation_element, |
| bool is_manually_triggered) { |
| DCHECK(client_->IsSavingAndFillingEnabled(form.origin)); |
| |
| PasswordFormManagerInterface* form_manager = GetMatchedManager(driver, form); |
| if (form_manager) { |
| form_manager->SetGenerationElement(generation_element); |
| form_manager->SetGenerationPopupWasShown(true, is_manually_triggered); |
| } |
| } |
| |
| void PasswordManager::DidNavigateMainFrame(bool form_may_be_submitted) { |
| std::unique_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (password_manager_util::IsLoggingActive(client_)) { |
| logger.reset( |
| new BrowserSavePasswordProgressLogger(client_->GetLogManager())); |
| logger->LogBoolean(Logger::STRING_DID_NAVIGATE_MAIN_FRAME, |
| form_may_be_submitted); |
| } |
| |
| if (client_->IsNewTabPage()) { |
| if (logger) |
| logger->LogMessage(Logger::STRING_NAVIGATION_NTP); |
| // On a successful Chrome sign-in the page navigates to the new tab page |
| // (ntp). OnPasswordFormsRendered is not called on ntp. That is why the |
| // standard flow for saving hash does not work. Save a password hash now |
| // since a navigation to ntp is the sign of successful sign-in. |
| PasswordFormManagerInterface* manager = GetSubmittedManager(); |
| if (manager && manager->GetSubmittedForm() |
| ->form_data.is_gaia_with_skip_save_password_form) { |
| MaybeSavePasswordHash(manager); |
| } |
| } |
| |
| for (std::unique_ptr<PasswordFormManager>& manager : form_managers_) { |
| if (form_may_be_submitted && manager->is_submitted()) { |
| owned_submitted_form_manager_ = std::move(manager); |
| break; |
| } |
| } |
| form_managers_.clear(); |
| predictions_.clear(); |
| store_password_called_ = false; |
| } |
| |
| void PasswordManager::UpdateFormManagers() { |
| std::vector<PasswordFormManagerInterface*> form_managers; |
| for (const auto& form_manager : form_managers_) |
| form_managers.push_back(form_manager.get()); |
| |
| // Get the fetchers and all the drivers. |
| std::vector<FormFetcher*> fetchers; |
| std::vector<PasswordManagerDriver*> drivers; |
| for (PasswordFormManagerInterface* form_manager : form_managers) { |
| fetchers.push_back(form_manager->GetFormFetcher()); |
| for (const auto& driver : form_manager->GetDrivers()) { |
| if (driver) |
| drivers.push_back(driver.get()); |
| } |
| } |
| |
| // Remove the duplicates. |
| std::sort(fetchers.begin(), fetchers.end()); |
| fetchers.erase(std::unique(fetchers.begin(), fetchers.end()), fetchers.end()); |
| std::sort(drivers.begin(), drivers.end()); |
| drivers.erase(std::unique(drivers.begin(), drivers.end()), drivers.end()); |
| // Refetch credentials for all the forms and update the drivers. |
| for (FormFetcher* fetcher : fetchers) |
| fetcher->Fetch(); |
| |
| // The autofill manager will be repopulated again when the credentials |
| // are retrieved. |
| for (PasswordManagerDriver* driver : drivers) { |
| // GetPasswordAutofillManager() is returning nullptr in iOS Chrome, since |
| // PasswordAutofillManager is not instantiated on iOS Chrome. |
| // See //ios/chrome/browser/passwords/ios_chrome_password_manager_driver.mm |
| if (driver->GetPasswordAutofillManager()) { |
| driver->GetPasswordAutofillManager()->DeleteFillData(); |
| } |
| } |
| } |
| |
| void PasswordManager::DropFormManagers() { |
| form_managers_.clear(); |
| owned_submitted_form_manager_.reset(); |
| all_visible_forms_.clear(); |
| predictions_.clear(); |
| } |
| |
| bool PasswordManager::IsPasswordFieldDetectedOnPage() { |
| return !form_managers_.empty(); |
| } |
| |
| void PasswordManager::OnPasswordFormSubmitted( |
| password_manager::PasswordManagerDriver* driver, |
| const PasswordForm& password_form) { |
| ProvisionallySaveForm(password_form.form_data, driver, false); |
| } |
| |
| |
| void PasswordManager::OnPasswordFormSubmittedNoChecks( |
| password_manager::PasswordManagerDriver* driver, |
| const PasswordForm& password_form) { |
| if (password_manager_util::IsLoggingActive(client_)) { |
| BrowserSavePasswordProgressLogger logger(client_->GetLogManager()); |
| logger.LogMessage(Logger::STRING_ON_SAME_DOCUMENT_NAVIGATION); |
| } |
| |
| if (gaia::IsGaiaSignonRealm(GURL(password_form.signon_realm)) && |
| !IsThereVisiblePasswordField(password_form.form_data)) { |
| // Gaia form without visible password fields is found. |
| // It might happen only when Password Manager autofilled a username |
| // (visible) and a password (invisible) fields. Then the user typed a new |
| // username. A page removed the form. As result a form is inconsistent - the |
| // username from one account, the password from another. Skip such form. |
| return; |
| } |
| |
| ProvisionallySaveForm(password_form.form_data, driver, false); |
| |
| if (IsAutomaticSavePromptAvailable()) |
| OnLoginSuccessful(); |
| } |
| |
| void PasswordManager::OnUserModifiedNonPasswordField( |
| password_manager::PasswordManagerDriver* driver, |
| int32_t renderer_id, |
| const base::string16& value) { |
| // TODO(https://crbug.com/959776): Implemented processing |value| as possible |
| // username for username first flow. |
| } |
| |
| void PasswordManager::ShowManualFallbackForSaving( |
| password_manager::PasswordManagerDriver* driver, |
| const PasswordForm& password_form) { |
| PasswordFormManager* manager = |
| ProvisionallySaveForm(password_form.form_data, driver, true); |
| |
| if (manager && password_form.form_data.is_gaia_with_skip_save_password_form) { |
| manager->GetMetricsRecorder() |
| ->set_user_typed_password_on_chrome_sign_in_page(); |
| } |
| |
| if (!client_->GetProfilePasswordStore()->IsAbleToSavePasswords() || |
| !client_->IsSavingAndFillingEnabled(password_form.origin) || |
| ShouldBlockPasswordForSameOriginButDifferentScheme( |
| password_form.origin) || |
| !client_->GetStoreResultFilter()->ShouldSave(password_form)) { |
| return; |
| } |
| |
| auto availability = |
| manager ? PasswordManagerMetricsRecorder::FormManagerAvailable::kSuccess |
| : PasswordManagerMetricsRecorder::FormManagerAvailable:: |
| kMissingManual; |
| if (client_ && client_->GetMetricsRecorder()) |
| client_->GetMetricsRecorder()->RecordFormManagerAvailable(availability); |
| if (!manager) |
| return; |
| |
| // Show the fallback if a prompt or a confirmation bubble should be available. |
| bool has_generated_password = manager->HasGeneratedPassword(); |
| if (ShouldPromptUserToSavePassword(*manager) || has_generated_password) { |
| bool is_update = manager->IsPasswordUpdate(); |
| manager->GetMetricsRecorder()->RecordShowManualFallbackForSaving( |
| has_generated_password, is_update); |
| client_->ShowManualFallbackForSaving(manager->Clone(), |
| has_generated_password, is_update); |
| } else { |
| HideManualFallbackForSaving(); |
| } |
| } |
| |
| void PasswordManager::HideManualFallbackForSaving() { |
| client_->HideManualFallbackForSaving(); |
| } |
| |
| void PasswordManager::OnPasswordFormsParsed( |
| password_manager::PasswordManagerDriver* driver, |
| const std::vector<PasswordForm>& forms) { |
| CreatePendingLoginManagers(driver, forms); |
| |
| PasswordGenerationFrameHelper* password_generation_manager = |
| driver ? driver->GetPasswordGenerationHelper() : nullptr; |
| if (password_generation_manager) { |
| password_generation_manager->PrefetchSpec( |
| client_->GetLastCommittedEntryURL().GetOrigin()); |
| } |
| } |
| |
| void PasswordManager::CreatePendingLoginManagers( |
| password_manager::PasswordManagerDriver* driver, |
| const std::vector<PasswordForm>& forms) { |
| std::unique_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (password_manager_util::IsLoggingActive(client_)) { |
| logger.reset( |
| new BrowserSavePasswordProgressLogger(client_->GetLogManager())); |
| logger->LogMessage(Logger::STRING_CREATE_LOGIN_MANAGERS_METHOD); |
| } |
| |
| CreateFormManagers(driver, forms); |
| |
| // Record whether or not this top-level URL has at least one password field. |
| client_->AnnotateNavigationEntry(!forms.empty()); |
| |
| // Only report SSL error status for cases where there are potentially forms to |
| // fill or save from. |
| if (!forms.empty()) { |
| metrics_util::CertificateError cert_error = |
| metrics_util::CertificateError::NONE; |
| const net::CertStatus cert_status = client_->GetMainFrameCertStatus(); |
| // The order of the if statements matters -- if the status involves multiple |
| // errors, Chrome should report the one highest up in the list below. |
| if (cert_status & net::CERT_STATUS_AUTHORITY_INVALID) |
| cert_error = metrics_util::CertificateError::AUTHORITY_INVALID; |
| else if (cert_status & net::CERT_STATUS_COMMON_NAME_INVALID) |
| cert_error = metrics_util::CertificateError::COMMON_NAME_INVALID; |
| else if (cert_status & net::CERT_STATUS_WEAK_SIGNATURE_ALGORITHM) |
| cert_error = metrics_util::CertificateError::WEAK_SIGNATURE_ALGORITHM; |
| else if (cert_status & net::CERT_STATUS_DATE_INVALID) |
| cert_error = metrics_util::CertificateError::DATE_INVALID; |
| else if (net::IsCertStatusError(cert_status)) |
| cert_error = metrics_util::CertificateError::OTHER; |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "PasswordManager.CertificateErrorsWhileSeeingForms", cert_error, |
| metrics_util::CertificateError::COUNT); |
| } |
| } |
| |
| void PasswordManager::CreateFormManagers( |
| password_manager::PasswordManagerDriver* driver, |
| const std::vector<PasswordForm>& forms) { |
| // Find new forms. |
| std::vector<const PasswordForm*> new_forms; |
| for (const PasswordForm& form : forms) { |
| if (!client_->IsFillingEnabled(form.origin)) |
| continue; |
| |
| PasswordFormManager* manager = |
| FindMatchedManager(form.form_data, form_managers_, driver); |
| |
| if (manager) { |
| // This extra filling is just duplicating redundancy that was in |
| // PasswordFormManager, that helps to fix cases when the site overrides |
| // filled values. |
| // TODO(https://crbug.com/831123): Implement more robust filling and |
| // remove the next line. |
| manager->FillForm(form.form_data); |
| } else { |
| new_forms.push_back(&form); |
| } |
| } |
| |
| // Create form manager for new forms. |
| for (const PasswordForm* new_form : new_forms) { |
| auto* manager = CreateFormManager(driver, new_form->form_data); |
| manager->set_old_parsing_result(*new_form); |
| } |
| } |
| |
| PasswordFormManager* PasswordManager::CreateFormManager( |
| PasswordManagerDriver* driver, |
| const autofill::FormData& form) { |
| form_managers_.push_back(std::make_unique<PasswordFormManager>( |
| client_, |
| driver ? driver->AsWeakPtr() : base::WeakPtr<PasswordManagerDriver>(), |
| form, nullptr, |
| std::make_unique<FormSaverImpl>(client_->GetProfilePasswordStore()), |
| nullptr)); |
| form_managers_.back()->ProcessServerPredictions(predictions_); |
| return form_managers_.back().get(); |
| } |
| |
| PasswordFormManager* PasswordManager::ProvisionallySaveForm( |
| const FormData& submitted_form, |
| PasswordManagerDriver* driver, |
| bool is_manual_fallback) { |
| std::unique_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (password_manager_util::IsLoggingActive(client_)) { |
| logger.reset( |
| new BrowserSavePasswordProgressLogger(client_->GetLogManager())); |
| logger->LogMessage(Logger::STRING_PROVISIONALLY_SAVE_FORM_METHOD); |
| } |
| if (!client_->IsSavingAndFillingEnabled(submitted_form.url)) { |
| RecordProvisionalSaveFailure( |
| PasswordManagerMetricsRecorder::SAVING_DISABLED, submitted_form.url, |
| logger.get()); |
| return nullptr; |
| } |
| |
| if (store_password_called_) |
| return nullptr; |
| |
| // No need to report PasswordManagerMetricsRecorder::EMPTY_PASSWORD, because |
| // PasswordToSave in PasswordFormManager DCHECKs that the password is never |
| // empty. |
| |
| const GURL& origin = submitted_form.url; |
| if (ShouldBlockPasswordForSameOriginButDifferentScheme(origin)) { |
| RecordProvisionalSaveFailure( |
| PasswordManagerMetricsRecorder::SAVING_ON_HTTP_AFTER_HTTPS, origin, |
| logger.get()); |
| return nullptr; |
| } |
| |
| PasswordFormManager* matched_manager = |
| GetMatchedManager(driver, submitted_form); |
| |
| auto availability = |
| matched_manager |
| ? PasswordManagerMetricsRecorder::FormManagerAvailable::kSuccess |
| : PasswordManagerMetricsRecorder::FormManagerAvailable:: |
| kMissingProvisionallySave; |
| if (client_ && client_->GetMetricsRecorder()) |
| client_->GetMetricsRecorder()->RecordFormManagerAvailable(availability); |
| |
| if (!matched_manager) { |
| RecordProvisionalSaveFailure( |
| PasswordManagerMetricsRecorder::NO_MATCHING_FORM, submitted_form.url, |
| logger.get()); |
| matched_manager = CreateFormManager(driver, submitted_form); |
| } |
| |
| if (is_manual_fallback && matched_manager->GetFormFetcher()->GetState() == |
| FormFetcher::State::WAITING) { |
| // In case of manual fallback, the form manager has to be ready for saving. |
| return nullptr; |
| } |
| |
| if (!matched_manager->ProvisionallySave(submitted_form, driver)) |
| return nullptr; |
| |
| // Set all other form managers to no submission state. |
| for (const auto& manager : form_managers_) { |
| if (manager.get() != matched_manager) |
| manager->set_not_submitted(); |
| } |
| |
| // Cache the user-visible URL (i.e., the one seen in the omnibox). Once the |
| // post-submit navigation concludes, we compare the landing URL against the |
| // cached and report the difference through UMA. |
| main_frame_url_ = client_->GetMainFrameURL(); |
| |
| ReportSubmittedFormFrameMetric(driver, *matched_manager->GetSubmittedForm()); |
| |
| return matched_manager; |
| } |
| |
| void PasswordManager::LogFirstFillingResult(PasswordManagerDriver* driver, |
| uint32_t form_renderer_id, |
| int32_t result) { |
| PasswordFormManager* matching_manager = |
| FindMatchedManagerByRendererId(form_renderer_id, form_managers_, driver); |
| if (!matching_manager) |
| return; |
| matching_manager->GetMetricsRecorder()->RecordFirstFillingResult(result); |
| } |
| |
| void PasswordManager::NotifyStorePasswordCalled() { |
| store_password_called_ = true; |
| DropFormManagers(); |
| } |
| |
| #if defined(OS_IOS) |
| void PasswordManager::PresaveGeneratedPassword( |
| PasswordManagerDriver* driver, |
| const FormData& form, |
| const base::string16& generated_password, |
| const base::string16& generation_element) { |
| PasswordFormManager* form_manager = |
| FindMatchedManager(form, form_managers_, driver); |
| UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager", |
| !form_manager); |
| |
| // TODO(https://crbug.com/886583): Create form manager if not found. |
| if (form_manager) { |
| form_manager->PresaveGeneratedPassword(driver, form, generated_password, |
| generation_element); |
| } |
| } |
| |
| void PasswordManager::UpdateGeneratedPasswordOnUserInput( |
| const base::string16& form_identifier, |
| const base::string16& field_identifier, |
| const base::string16& field_value) { |
| for (std::unique_ptr<PasswordFormManager>& manager : form_managers_) { |
| if (manager->UpdateGeneratedPasswordOnUserInput( |
| form_identifier, field_identifier, field_value)) { |
| break; |
| } |
| } |
| } |
| |
| void PasswordManager::OnPasswordNoLongerGenerated( |
| PasswordManagerDriver* driver) { |
| for (std::unique_ptr<PasswordFormManager>& manager : form_managers_) |
| manager->PasswordNoLongerGenerated(); |
| } |
| #endif |
| |
| bool PasswordManager::IsAutomaticSavePromptAvailable() { |
| std::unique_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (password_manager_util::IsLoggingActive(client_)) { |
| logger.reset( |
| new BrowserSavePasswordProgressLogger(client_->GetLogManager())); |
| logger->LogMessage(Logger::STRING_CAN_PROVISIONAL_MANAGER_SAVE_METHOD); |
| } |
| |
| PasswordFormManagerInterface* submitted_manager = GetSubmittedManager(); |
| |
| if (!submitted_manager) { |
| if (logger) { |
| logger->LogMessage(Logger::STRING_NO_PROVISIONAL_SAVE_MANAGER); |
| } |
| return false; |
| } |
| |
| if (submitted_manager->GetFormFetcher()->GetState() == |
| FormFetcher::State::WAITING) { |
| // We have a provisional save manager, but it didn't finish matching yet. |
| // We just give up. |
| RecordProvisionalSaveFailure( |
| PasswordManagerMetricsRecorder::MATCHING_NOT_COMPLETE, |
| submitted_manager->GetOrigin(), logger.get()); |
| return false; |
| } |
| |
| return !submitted_manager->GetPendingCredentials().only_for_fallback; |
| } |
| |
| bool PasswordManager::ShouldBlockPasswordForSameOriginButDifferentScheme( |
| const GURL& origin) const { |
| const GURL& old_origin = main_frame_url_.GetOrigin(); |
| return old_origin.host_piece() == origin.host_piece() && |
| old_origin.SchemeIsCryptographic() && !origin.SchemeIsCryptographic(); |
| } |
| |
| void PasswordManager::OnPasswordFormsRendered( |
| password_manager::PasswordManagerDriver* driver, |
| const std::vector<PasswordForm>& visible_forms, |
| bool did_stop_loading) { |
| CreatePendingLoginManagers(driver, visible_forms); |
| std::unique_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (password_manager_util::IsLoggingActive(client_)) { |
| logger.reset( |
| new BrowserSavePasswordProgressLogger(client_->GetLogManager())); |
| logger->LogMessage(Logger::STRING_ON_PASSWORD_FORMS_RENDERED_METHOD); |
| } |
| |
| if (!IsAutomaticSavePromptAvailable()) |
| return; |
| |
| PasswordFormManagerInterface* submitted_manager = GetSubmittedManager(); |
| |
| // If the server throws an internal error, access denied page, page not |
| // found etc. after a login attempt, we do not save the credentials. |
| if (client_->WasLastNavigationHTTPError()) { |
| if (logger) |
| logger->LogMessage(Logger::STRING_DECISION_DROP); |
| submitted_manager->GetMetricsRecorder()->LogSubmitFailed(); |
| owned_submitted_form_manager_.reset(); |
| return; |
| } |
| |
| if (logger) { |
| logger->LogNumber(Logger::STRING_NUMBER_OF_VISIBLE_FORMS, |
| visible_forms.size()); |
| } |
| |
| // Record all visible forms from the frame. |
| all_visible_forms_.insert(all_visible_forms_.end(), |
| visible_forms.begin(), |
| visible_forms.end()); |
| |
| if (!did_stop_loading && |
| !submitted_manager->GetSubmittedForm() |
| ->form_data.is_gaia_with_skip_save_password_form) { |
| // |form_data.is_gaia_with_skip_save_password_form| = true means that this |
| // is a Chrome sign-in page. Chrome sign-in pages are redirected to an empty |
| // pages, and for some reasons |did_stop_loading| might be false. So |
| // |did_stop_loading| is ignored for them. |
| return; |
| } |
| |
| // If we see the login form again, then the login failed. |
| if (submitted_manager->GetPendingCredentials().scheme == |
| PasswordForm::Scheme::kHtml) { |
| for (const PasswordForm& form : all_visible_forms_) { |
| if (IsPasswordFormReappeared( |
| form, submitted_manager->GetPendingCredentials())) { |
| if (submitted_manager->IsPossibleChangePasswordFormWithoutUsername() && |
| AreAllFieldsEmpty(form)) { |
| continue; |
| } |
| submitted_manager->GetMetricsRecorder()->LogSubmitFailed(); |
| if (logger) { |
| logger->LogPasswordForm(Logger::STRING_PASSWORD_FORM_REAPPEARED, |
| form); |
| logger->LogMessage(Logger::STRING_DECISION_DROP); |
| } |
| owned_submitted_form_manager_.reset(); |
| // Clear all_visible_forms_ once we found the match. |
| all_visible_forms_.clear(); |
| return; |
| } |
| } |
| } else { |
| if (logger) |
| logger->LogMessage(Logger::STRING_PROVISIONALLY_SAVED_FORM_IS_NOT_HTML); |
| } |
| |
| // Clear all_visible_forms_ after checking all the visible forms. |
| all_visible_forms_.clear(); |
| |
| // Looks like a successful login attempt. Either show an infobar or |
| // automatically save the login data. We prompt when the user hasn't |
| // already given consent, either through previously accepting the infobar |
| // or by having the browser generate the password. |
| OnLoginSuccessful(); |
| } |
| |
| void PasswordManager::OnLoginSuccessful() { |
| std::unique_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (password_manager_util::IsLoggingActive(client_)) { |
| logger.reset( |
| new BrowserSavePasswordProgressLogger(client_->GetLogManager())); |
| logger->LogMessage(Logger::STRING_ON_ASK_USER_OR_SAVE_PASSWORD); |
| } |
| |
| PasswordFormManagerInterface* submitted_manager = GetSubmittedManager(); |
| DCHECK(submitted_manager); |
| DCHECK(submitted_manager->GetSubmittedForm()); |
| |
| client_->GetStoreResultFilter()->ReportFormLoginSuccess(*submitted_manager); |
| #if !defined(OS_IOS) |
| leak_delegate_.StartLeakCheck(submitted_manager->GetPendingCredentials()); |
| #endif |
| |
| auto submission_event = |
| submitted_manager->GetSubmittedForm()->submission_event; |
| metrics_util::LogPasswordSuccessfulSubmissionIndicatorEvent(submission_event); |
| if (logger) |
| logger->LogSuccessfulSubmissionIndicatorEvent(submission_event); |
| |
| bool able_to_save_passwords = |
| client_->GetProfilePasswordStore()->IsAbleToSavePasswords(); |
| UMA_HISTOGRAM_BOOLEAN("PasswordManager.AbleToSavePasswordsOnSuccessfulLogin", |
| able_to_save_passwords); |
| if (!able_to_save_passwords) |
| return; |
| |
| MaybeSavePasswordHash(submitted_manager); |
| |
| // TODO(https://crbug.com/831123): Implement checking whether to save with |
| // PasswordFormManager. |
| if (!client_->GetStoreResultFilter()->ShouldSave( |
| *submitted_manager->GetSubmittedForm())) { |
| RecordProvisionalSaveFailure( |
| PasswordManagerMetricsRecorder::SYNC_CREDENTIAL, |
| submitted_manager->GetOrigin(), logger.get()); |
| owned_submitted_form_manager_.reset(); |
| return; |
| } |
| |
| submitted_manager->GetMetricsRecorder()->LogSubmitPassed(); |
| |
| UMA_HISTOGRAM_BOOLEAN( |
| "PasswordManager.SuccessfulLoginHappened", |
| submitted_manager->GetSubmittedForm()->origin.SchemeIsCryptographic()); |
| |
| // If the form is eligible only for saving fallback, it shouldn't go here. |
| DCHECK(!submitted_manager->GetPendingCredentials().only_for_fallback); |
| |
| if (ShouldPromptUserToSavePassword(*submitted_manager)) { |
| if (logger) |
| logger->LogMessage(Logger::STRING_DECISION_ASK); |
| bool update_password = submitted_manager->IsPasswordUpdate(); |
| bool is_blacklisted = submitted_manager->IsBlacklisted(); |
| SyncState password_sync_state = client_->GetPasswordSyncState(); |
| if (ShouldShowOnboarding( |
| client_->GetPrefs(), PasswordUpdateBool(update_password), |
| BlacklistedBool(is_blacklisted), password_sync_state)) { |
| if (client_->ShowOnboarding(MoveOwnedSubmittedManager())) { |
| if (logger) |
| logger->LogMessage(Logger::STRING_SHOW_ONBOARDING); |
| } |
| } else if (client_->PromptUserToSaveOrUpdatePassword( |
| MoveOwnedSubmittedManager(), update_password)) { |
| if (logger) |
| logger->LogMessage(Logger::STRING_SHOW_PASSWORD_PROMPT); |
| } |
| } else { |
| if (logger) |
| logger->LogMessage(Logger::STRING_DECISION_SAVE); |
| submitted_manager->Save(); |
| |
| if (!submitted_manager->IsNewLogin()) { |
| client_->NotifySuccessfulLoginWithExistingPassword( |
| submitted_manager->GetPendingCredentials()); |
| } |
| |
| if (submitted_manager->HasGeneratedPassword()) |
| client_->AutomaticPasswordSave(MoveOwnedSubmittedManager()); |
| } |
| owned_submitted_form_manager_.reset(); |
| } |
| |
| void PasswordManager::MaybeSavePasswordHash( |
| PasswordFormManagerInterface* submitted_manager) { |
| #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) |
| const PasswordForm* submitted_form = submitted_manager->GetSubmittedForm(); |
| // When |username_value| is empty, it's not clear whether the submitted |
| // credentials are really Gaia or enterprise credentials. Don't save |
| // password hash in that case. |
| std::string username = base::UTF16ToUTF8(submitted_form->username_value); |
| if (username.empty()) |
| return; |
| |
| password_manager::PasswordStore* store = client_->GetProfilePasswordStore(); |
| // May be null in tests. |
| if (!store) |
| return; |
| |
| bool should_save_enterprise_pw = |
| client_->GetStoreResultFilter()->ShouldSaveEnterprisePasswordHash( |
| *submitted_form); |
| bool should_save_gaia_pw = |
| client_->GetStoreResultFilter()->ShouldSaveGaiaPasswordHash( |
| *submitted_form); |
| |
| if (!should_save_enterprise_pw && !should_save_gaia_pw) |
| return; |
| |
| if (submitted_form->form_data.is_gaia_with_skip_save_password_form) { |
| submitted_manager->GetMetricsRecorder() |
| ->set_password_hash_saved_on_chrome_sing_in_page(); |
| } |
| |
| if (password_manager_util::IsLoggingActive(client_)) { |
| BrowserSavePasswordProgressLogger logger(client_->GetLogManager()); |
| logger.LogMessage(Logger::STRING_SAVE_PASSWORD_HASH); |
| } |
| |
| // Canonicalizes username if it is an email. |
| if (username.find('@') != std::string::npos) |
| username = gaia::CanonicalizeEmail(username); |
| bool is_password_change = !submitted_form->new_password_element.empty(); |
| const base::string16 password = is_password_change |
| ? submitted_form->new_password_value |
| : submitted_form->password_value; |
| |
| if (should_save_enterprise_pw) { |
| store->SaveEnterprisePasswordHash(username, password); |
| return; |
| } |
| |
| DCHECK(should_save_gaia_pw); |
| GaiaPasswordHashChange event = |
| client_->GetStoreResultFilter()->IsSyncAccountEmail(username) |
| ? (is_password_change |
| ? GaiaPasswordHashChange::CHANGED_IN_CONTENT_AREA |
| : GaiaPasswordHashChange::SAVED_IN_CONTENT_AREA) |
| : GaiaPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE; |
| store->SaveGaiaPasswordHash(username, password, event); |
| #endif |
| } |
| |
| void PasswordManager::ProcessAutofillPredictions( |
| PasswordManagerDriver* driver, |
| const std::vector<FormStructure*>& forms) { |
| std::unique_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (password_manager_util::IsLoggingActive(client_)) { |
| logger.reset( |
| new BrowserSavePasswordProgressLogger(client_->GetLogManager())); |
| } |
| |
| for (const FormStructure* form : forms) |
| predictions_[form->form_signature()] = ConvertToFormPredictions(*form); |
| for (auto& manager : form_managers_) |
| manager->ProcessServerPredictions(predictions_); |
| |
| // Create form managers for non-password forms with single usernames. |
| for (const FormStructure* form : forms) { |
| if (form->has_password_field()) |
| continue; |
| |
| // Do not skip the form if it either contains a field for the Username |
| // first flow or a clear-text password field. |
| if (!(HasSingleUsernameVote(*form) || HasNewPasswordVote(*form))) |
| continue; |
| |
| if (FindMatchedManagerByRendererId(form->unique_renderer_id(), |
| form_managers_, driver)) { |
| // The form manager is already created. |
| continue; |
| } |
| |
| FormData form_data = form->ToFormData(); |
| auto* manager = CreateFormManager(driver, form_data); |
| manager->ProcessServerPredictions(predictions_); |
| } |
| |
| } |
| |
| PasswordFormManagerInterface* PasswordManager::GetSubmittedManager() const { |
| if (owned_submitted_form_manager_) |
| return owned_submitted_form_manager_.get(); |
| |
| for (const std::unique_ptr<PasswordFormManager>& manager : form_managers_) { |
| if (manager->is_submitted()) |
| return manager.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| std::unique_ptr<PasswordFormManagerForUI> |
| PasswordManager::MoveOwnedSubmittedManager() { |
| if (owned_submitted_form_manager_) |
| return std::move(owned_submitted_form_manager_); |
| |
| for (auto iter = form_managers_.begin(); iter != form_managers_.end(); |
| ++iter) { |
| if ((*iter)->is_submitted()) { |
| std::unique_ptr<PasswordFormManager> submitted_manager = std::move(*iter); |
| form_managers_.erase(iter); |
| return std::move(submitted_manager); |
| } |
| } |
| |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| void PasswordManager::RecordProvisionalSaveFailure( |
| PasswordManagerMetricsRecorder::ProvisionalSaveFailure failure, |
| const GURL& form_origin, |
| BrowserSavePasswordProgressLogger* logger) { |
| if (client_ && client_->GetMetricsRecorder()) { |
| client_->GetMetricsRecorder()->RecordProvisionalSaveFailure( |
| failure, main_frame_url_, form_origin, logger); |
| } |
| } |
| |
| // TODO(https://crbug.com/831123): Implement creating missing |
| // PasswordFormManager when PasswordFormManager is gone. |
| PasswordFormManagerInterface* PasswordManager::GetMatchedManager( |
| const PasswordManagerDriver* driver, |
| const PasswordForm& form) { |
| return GetMatchedManager(driver, form.form_data); |
| } |
| |
| PasswordFormManager* PasswordManager::GetMatchedManager( |
| const PasswordManagerDriver* driver, |
| const FormData& form) { |
| for (auto& form_manager : form_managers_) { |
| if (form_manager->DoesManage(form, driver)) |
| return form_manager.get(); |
| } |
| return nullptr; |
| } |
| |
| void PasswordManager::ReportSubmittedFormFrameMetric( |
| const PasswordManagerDriver* driver, |
| const PasswordForm& form) { |
| if (!driver) |
| return; |
| metrics_util::SubmittedFormFrame frame; |
| if (driver->IsMainFrame()) { |
| frame = metrics_util::SubmittedFormFrame::MAIN_FRAME; |
| } else if (form.origin == main_frame_url_) { |
| frame = |
| metrics_util::SubmittedFormFrame::IFRAME_WITH_SAME_URL_AS_MAIN_FRAME; |
| } else { |
| GURL::Replacements rep; |
| rep.SetPathStr(""); |
| std::string main_frame_signon_realm = |
| main_frame_url_.ReplaceComponents(rep).spec(); |
| frame = (main_frame_signon_realm == form.signon_realm) |
| ? metrics_util::SubmittedFormFrame:: |
| IFRAME_WITH_DIFFERENT_URL_SAME_SIGNON_REALM_AS_MAIN_FRAME |
| : metrics_util::SubmittedFormFrame:: |
| IFRAME_WITH_DIFFERENT_SIGNON_REALM; |
| } |
| metrics_util::LogSubmittedFormFrame(frame); |
| } |
| |
| } // namespace password_manager |