| // 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 "base/command_line.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/histogram.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/platform_thread.h" |
| #include "components/autofill/core/common/password_autofill_util.h" |
| #include "components/password_manager/core/browser/browser_save_password_progress_logger.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_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/common/password_manager_pref_names.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| |
| using autofill::PasswordForm; |
| using autofill::PasswordFormMap; |
| |
| namespace password_manager { |
| |
| namespace { |
| |
| const char kSpdyProxyRealm[] = "/SpdyProxy"; |
| |
| // Shorten the name to spare line breaks. The code provides enough context |
| // already. |
| typedef autofill::SavePasswordProgressLogger Logger; |
| |
| // This routine is called when PasswordManagers are constructed. |
| // |
| // Currently we report metrics only once at startup. We require |
| // that this is only ever called from a single thread in order to |
| // avoid needing to lock (a static boolean flag is then sufficient to |
| // guarantee running only once). |
| void ReportMetrics(bool password_manager_enabled) { |
| static base::PlatformThreadId initial_thread_id = |
| base::PlatformThread::CurrentId(); |
| DCHECK(initial_thread_id == base::PlatformThread::CurrentId()); |
| |
| static bool ran_once = false; |
| if (ran_once) |
| return; |
| ran_once = true; |
| |
| UMA_HISTOGRAM_BOOLEAN("PasswordManager.Enabled", password_manager_enabled); |
| } |
| |
| } // namespace |
| |
| const char PasswordManager::kOtherPossibleUsernamesExperiment[] = |
| "PasswordManagerOtherPossibleUsernames"; |
| |
| // static |
| void PasswordManager::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref( |
| prefs::kPasswordManagerEnabled, |
| true, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterBooleanPref( |
| prefs::kPasswordManagerAllowShowPasswords, |
| true, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| registry->RegisterListPref(prefs::kPasswordManagerGroupsForDomains, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| } |
| |
| PasswordManager::PasswordManager(PasswordManagerClient* client) |
| : client_(client), driver_(client->GetDriver()) { |
| DCHECK(client_); |
| DCHECK(driver_); |
| password_manager_enabled_.Init(prefs::kPasswordManagerEnabled, |
| client_->GetPrefs()); |
| |
| ReportMetrics(*password_manager_enabled_); |
| } |
| |
| PasswordManager::~PasswordManager() { |
| FOR_EACH_OBSERVER(LoginModelObserver, observers_, OnLoginModelDestroying()); |
| } |
| |
| void PasswordManager::SetFormHasGeneratedPassword(const PasswordForm& form) { |
| for (ScopedVector<PasswordFormManager>::iterator iter = |
| pending_login_managers_.begin(); |
| iter != pending_login_managers_.end(); |
| ++iter) { |
| if ((*iter)->DoesManage(form, PasswordFormManager::ACTION_MATCH_REQUIRED)) { |
| (*iter)->SetHasGeneratedPassword(); |
| return; |
| } |
| } |
| // If there is no corresponding PasswordFormManager, we create one. This is |
| // not the common case, and should only happen when there is a bug in our |
| // ability to detect forms. |
| bool ssl_valid = (form.origin.SchemeIsSecure() && |
| !driver_->DidLastPageLoadEncounterSSLErrors()); |
| PasswordFormManager* manager = |
| new PasswordFormManager(this, client_, driver_, form, ssl_valid); |
| pending_login_managers_.push_back(manager); |
| manager->SetHasGeneratedPassword(); |
| // TODO(gcasto): Add UMA stats to track this. |
| } |
| |
| bool PasswordManager::IsSavingEnabled() const { |
| return *password_manager_enabled_ && !driver_->IsOffTheRecord(); |
| } |
| |
| void PasswordManager::ProvisionallySavePassword(const PasswordForm& form) { |
| bool is_saving_enabled = IsSavingEnabled(); |
| |
| scoped_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (client_->IsLoggingActive()) { |
| logger.reset(new BrowserSavePasswordProgressLogger(client_)); |
| logger->LogMessage(Logger::STRING_PROVISIONALLY_SAVE_PASSWORD_METHOD); |
| logger->LogPasswordForm(Logger::STRING_PROVISIONALLY_SAVE_PASSWORD_FORM, |
| form); |
| logger->LogBoolean(Logger::STRING_IS_SAVING_ENABLED, is_saving_enabled); |
| } |
| |
| if (!is_saving_enabled) { |
| RecordFailure(SAVING_DISABLED, form.origin.host(), logger.get()); |
| return; |
| } |
| |
| // No password to save? Then don't. |
| if (form.password_value.empty()) { |
| RecordFailure(EMPTY_PASSWORD, form.origin.host(), logger.get()); |
| return; |
| } |
| |
| scoped_ptr<PasswordFormManager> manager; |
| ScopedVector<PasswordFormManager>::iterator matched_manager_it = |
| pending_login_managers_.end(); |
| for (ScopedVector<PasswordFormManager>::iterator iter = |
| pending_login_managers_.begin(); |
| iter != pending_login_managers_.end(); |
| ++iter) { |
| if ((*iter)->DoesManage(form, PasswordFormManager::ACTION_MATCH_REQUIRED)) { |
| // If we find a manager that exactly matches the submitted form including |
| // the action URL, exit the loop. |
| if (logger) |
| logger->LogMessage(Logger::STRING_EXACT_MATCH); |
| matched_manager_it = iter; |
| break; |
| } else if ((*iter)->DoesManage( |
| form, PasswordFormManager::ACTION_MATCH_NOT_REQUIRED)) { |
| // If the current manager matches the submitted form excluding the action |
| // URL, remember it as a candidate and continue searching for an exact |
| // match. |
| if (logger) |
| logger->LogMessage(Logger::STRING_MATCH_WITHOUT_ACTION); |
| matched_manager_it = iter; |
| } |
| } |
| // If we didn't find a manager, this means a form was submitted without |
| // first loading the page containing the form. Don't offer to save |
| // passwords in this case. |
| if (matched_manager_it != pending_login_managers_.end()) { |
| // Transfer ownership of the manager from |pending_login_managers_| to |
| // |manager|. |
| manager.reset(*matched_manager_it); |
| pending_login_managers_.weak_erase(matched_manager_it); |
| } else { |
| RecordFailure(NO_MATCHING_FORM, form.origin.host(), logger.get()); |
| return; |
| } |
| |
| // If we found a manager but it didn't finish matching yet, the user has |
| // tried to submit credentials before we had time to even find matching |
| // results for the given form and autofill. If this is the case, we just |
| // give up. |
| if (!manager->HasCompletedMatching()) { |
| RecordFailure(MATCHING_NOT_COMPLETE, form.origin.host(), logger.get()); |
| return; |
| } |
| |
| // Also get out of here if the user told us to 'never remember' passwords for |
| // this form. |
| if (manager->IsBlacklisted()) { |
| RecordFailure(FORM_BLACKLISTED, form.origin.host(), logger.get()); |
| return; |
| } |
| |
| // Bail if we're missing any of the necessary form components. |
| if (!manager->HasValidPasswordForm()) { |
| RecordFailure(INVALID_FORM, form.origin.host(), logger.get()); |
| return; |
| } |
| |
| // Always save generated passwords, as the user expresses explicit intent for |
| // Chrome to manage such passwords. For other passwords, respect the |
| // autocomplete attribute if autocomplete='off' is not ignored. |
| if (!autofill::ShouldIgnoreAutocompleteOffForPasswordFields() && |
| !manager->HasGeneratedPassword() && !form.password_autocomplete_set) { |
| RecordFailure(AUTOCOMPLETE_OFF, form.origin.host(), logger.get()); |
| return; |
| } |
| |
| PasswordForm provisionally_saved_form(form); |
| provisionally_saved_form.ssl_valid = |
| form.origin.SchemeIsSecure() && |
| !driver_->DidLastPageLoadEncounterSSLErrors(); |
| provisionally_saved_form.preferred = true; |
| if (logger) { |
| logger->LogPasswordForm(Logger::STRING_PROVISIONALLY_SAVED_FORM, |
| provisionally_saved_form); |
| } |
| PasswordFormManager::OtherPossibleUsernamesAction action = |
| PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES; |
| if (OtherPossibleUsernamesEnabled()) |
| action = PasswordFormManager::ALLOW_OTHER_POSSIBLE_USERNAMES; |
| if (logger) { |
| logger->LogBoolean( |
| Logger::STRING_IGNORE_POSSIBLE_USERNAMES, |
| action == PasswordFormManager::IGNORE_OTHER_POSSIBLE_USERNAMES); |
| } |
| manager->ProvisionallySave(provisionally_saved_form, action); |
| provisional_save_manager_.swap(manager); |
| } |
| |
| void PasswordManager::RecordFailure(ProvisionalSaveFailure failure, |
| const std::string& form_origin, |
| BrowserSavePasswordProgressLogger* logger) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "PasswordManager.ProvisionalSaveFailure", failure, MAX_FAILURE_VALUE); |
| |
| std::string group_name = metrics_util::GroupIdToString( |
| metrics_util::MonitoredDomainGroupId(form_origin, client_->GetPrefs())); |
| if (!group_name.empty()) { |
| metrics_util::LogUMAHistogramEnumeration( |
| "PasswordManager.ProvisionalSaveFailure_" + group_name, |
| failure, |
| MAX_FAILURE_VALUE); |
| } |
| |
| if (logger) { |
| switch (failure) { |
| case SAVING_DISABLED: |
| logger->LogMessage(Logger::STRING_SAVING_DISABLED); |
| break; |
| case EMPTY_PASSWORD: |
| logger->LogMessage(Logger::STRING_EMPTY_PASSWORD); |
| break; |
| case MATCHING_NOT_COMPLETE: |
| logger->LogMessage(Logger::STRING_NO_FORM_MANAGER); |
| break; |
| case NO_MATCHING_FORM: |
| logger->LogMessage(Logger::STRING_NO_MATCHING_FORM); |
| break; |
| case FORM_BLACKLISTED: |
| logger->LogMessage(Logger::STRING_FORM_BLACKLISTED); |
| break; |
| case INVALID_FORM: |
| logger->LogMessage(Logger::STRING_INVALID_FORM); |
| break; |
| case AUTOCOMPLETE_OFF: |
| logger->LogMessage(Logger::STRING_AUTOCOMPLETE_OFF); |
| break; |
| case MAX_FAILURE_VALUE: |
| NOTREACHED(); |
| return; |
| } |
| logger->LogMessage(Logger::STRING_DECISION_DROP); |
| } |
| } |
| |
| void PasswordManager::AddSubmissionCallback( |
| const PasswordSubmittedCallback& callback) { |
| submission_callbacks_.push_back(callback); |
| } |
| |
| void PasswordManager::AddObserver(LoginModelObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void PasswordManager::RemoveObserver(LoginModelObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void PasswordManager::DidNavigateMainFrame(bool is_in_page) { |
| // Clear data after main frame navigation if the navigation was to a |
| // different page. |
| if (!is_in_page) { |
| pending_login_managers_.clear(); |
| driver_->GetPasswordAutofillManager()->Reset(); |
| } |
| } |
| |
| void PasswordManager::OnPasswordFormSubmitted( |
| const PasswordForm& password_form) { |
| ProvisionallySavePassword(password_form); |
| for (size_t i = 0; i < submission_callbacks_.size(); ++i) { |
| submission_callbacks_[i].Run(password_form); |
| } |
| |
| pending_login_managers_.clear(); |
| } |
| |
| void PasswordManager::OnPasswordFormsParsed( |
| const std::vector<PasswordForm>& forms) { |
| // Ask the SSLManager for current security. |
| bool had_ssl_error = driver_->DidLastPageLoadEncounterSSLErrors(); |
| |
| for (std::vector<PasswordForm>::const_iterator iter = forms.begin(); |
| iter != forms.end(); |
| ++iter) { |
| // Don't involve the password manager if this form corresponds to |
| // SpdyProxy authentication, as indicated by the realm. |
| if (EndsWith(iter->signon_realm, kSpdyProxyRealm, true)) |
| continue; |
| |
| bool ssl_valid = iter->origin.SchemeIsSecure() && !had_ssl_error; |
| PasswordFormManager* manager = |
| new PasswordFormManager(this, client_, driver_, *iter, ssl_valid); |
| pending_login_managers_.push_back(manager); |
| |
| // Avoid prompting the user for access to a password if they don't have |
| // password saving enabled. |
| PasswordStore::AuthorizationPromptPolicy prompt_policy = |
| *password_manager_enabled_ ? PasswordStore::ALLOW_PROMPT |
| : PasswordStore::DISALLOW_PROMPT; |
| |
| manager->FetchMatchingLoginsFromPasswordStore(prompt_policy); |
| } |
| } |
| |
| bool PasswordManager::ShouldPromptUserToSavePassword() const { |
| return !client_->IsAutomaticPasswordSavingEnabled() && |
| provisional_save_manager_->IsNewLogin() && |
| !provisional_save_manager_->HasGeneratedPassword() && |
| !provisional_save_manager_->IsPendingCredentialsPublicSuffixMatch(); |
| } |
| |
| void PasswordManager::OnPasswordFormsRendered( |
| const std::vector<PasswordForm>& visible_forms) { |
| scoped_ptr<BrowserSavePasswordProgressLogger> logger; |
| if (client_->IsLoggingActive()) { |
| logger.reset(new BrowserSavePasswordProgressLogger(client_)); |
| logger->LogMessage(Logger::STRING_ON_PASSWORD_FORMS_RENDERED_METHOD); |
| } |
| |
| if (!provisional_save_manager_.get()) { |
| if (logger) { |
| logger->LogMessage(Logger::STRING_NO_PROVISIONAL_SAVE_MANAGER); |
| logger->LogMessage(Logger::STRING_DECISION_DROP); |
| } |
| return; |
| } |
| |
| DCHECK(IsSavingEnabled()); |
| |
| if (logger) { |
| logger->LogNumber(Logger::STRING_NUMBER_OF_VISIBLE_FORMS, |
| visible_forms.size()); |
| } |
| |
| // If we see the login form again, then the login failed. |
| for (size_t i = 0; i < visible_forms.size(); ++i) { |
| // TODO(vabr): The similarity check is just action equality for now. If it |
| // becomes more complex, it may make sense to consider modifying and using |
| // PasswordFormManager::DoesManage for it. |
| if (visible_forms[i].action.is_valid() && |
| provisional_save_manager_->pending_credentials().action == |
| visible_forms[i].action) { |
| if (logger) { |
| logger->LogPasswordForm(Logger::STRING_PASSWORD_FORM_REAPPEARED, |
| visible_forms[i]); |
| logger->LogMessage(Logger::STRING_DECISION_DROP); |
| } |
| provisional_save_manager_->SubmitFailed(); |
| provisional_save_manager_.reset(); |
| return; |
| } |
| } |
| |
| // 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. |
| provisional_save_manager_->SubmitPassed(); |
| |
| if (ShouldPromptUserToSavePassword()) { |
| if (logger) |
| logger->LogMessage(Logger::STRING_DECISION_ASK); |
| client_->PromptUserToSavePassword(provisional_save_manager_.release()); |
| } else { |
| if (logger) |
| logger->LogMessage(Logger::STRING_DECISION_SAVE); |
| provisional_save_manager_->Save(); |
| provisional_save_manager_.reset(); |
| } |
| } |
| |
| void PasswordManager::PossiblyInitializeUsernamesExperiment( |
| const PasswordFormMap& best_matches) const { |
| if (base::FieldTrialList::Find(kOtherPossibleUsernamesExperiment)) |
| return; |
| |
| bool other_possible_usernames_exist = false; |
| for (autofill::PasswordFormMap::const_iterator it = best_matches.begin(); |
| it != best_matches.end(); |
| ++it) { |
| if (!it->second->other_possible_usernames.empty()) { |
| other_possible_usernames_exist = true; |
| break; |
| } |
| } |
| |
| if (!other_possible_usernames_exist) |
| return; |
| |
| const base::FieldTrial::Probability kDivisor = 100; |
| scoped_refptr<base::FieldTrial> trial( |
| base::FieldTrialList::FactoryGetFieldTrial( |
| kOtherPossibleUsernamesExperiment, |
| kDivisor, |
| "Disabled", |
| 2013, 12, 31, |
| base::FieldTrial::ONE_TIME_RANDOMIZED, |
| NULL)); |
| base::FieldTrial::Probability enabled_probability = |
| client_->GetProbabilityForExperiment(kOtherPossibleUsernamesExperiment); |
| trial->AppendGroup("Enabled", enabled_probability); |
| } |
| |
| bool PasswordManager::OtherPossibleUsernamesEnabled() const { |
| return base::FieldTrialList::FindFullName( |
| kOtherPossibleUsernamesExperiment) == "Enabled"; |
| } |
| |
| void PasswordManager::Autofill(const PasswordForm& form_for_autofill, |
| const PasswordFormMap& best_matches, |
| const PasswordForm& preferred_match, |
| bool wait_for_username) const { |
| PossiblyInitializeUsernamesExperiment(best_matches); |
| |
| // TODO(tedchoc): Switch to only requesting authentication if the user is |
| // acting on the autofilled forms (crbug.com/342594) instead |
| // of on page load. |
| bool authentication_required = preferred_match.use_additional_authentication; |
| for (autofill::PasswordFormMap::const_iterator it = best_matches.begin(); |
| !authentication_required && it != best_matches.end(); |
| ++it) { |
| if (it->second->use_additional_authentication) |
| authentication_required = true; |
| } |
| |
| switch (form_for_autofill.scheme) { |
| case PasswordForm::SCHEME_HTML: { |
| // Note the check above is required because the observers_ for a non-HTML |
| // schemed password form may have been freed, so we need to distinguish. |
| scoped_ptr<autofill::PasswordFormFillData> fill_data( |
| new autofill::PasswordFormFillData()); |
| InitPasswordFormFillData(form_for_autofill, |
| best_matches, |
| &preferred_match, |
| wait_for_username, |
| OtherPossibleUsernamesEnabled(), |
| fill_data.get()); |
| if (authentication_required) |
| client_->AuthenticateAutofillAndFillForm(fill_data.Pass()); |
| else |
| driver_->FillPasswordForm(*fill_data.get()); |
| break; |
| } |
| default: |
| FOR_EACH_OBSERVER( |
| LoginModelObserver, |
| observers_, |
| OnAutofillDataAvailable(preferred_match.username_value, |
| preferred_match.password_value)); |
| break; |
| } |
| |
| client_->PasswordWasAutofilled(best_matches); |
| } |
| |
| } // namespace password_manager |