blob: 6aa0418e00ff0f66948f26ba44bac450b28244d2 [file] [log] [blame]
// Copyright 2013 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_autofill_manager.h"
#include <vector>
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_driver.h"
#include "components/autofill/core/browser/popup_item_ids.h"
#include "components/autofill/core/browser/suggestion.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_data_validation.h"
#include "components/autofill/core/common/autofill_util.h"
#include "components/password_manager/core/browser/affiliation_utils.h"
#include "components/password_manager/core/browser/password_manager_driver.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/strings/grit/components_strings.h"
#include "grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
namespace password_manager {
namespace {
// Tests if |username| and |suggestion| are the same. This is different from the
// usual string operator== in that an empty username will only match the
// (non-empty) description of the empty username, used in the suggestions UI.
bool CompareUsernameSuggestion(const base::string16& username,
const base::string16& suggestion) {
if (username.empty()) {
return suggestion ==
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN);
}
return username == suggestion;
}
// Returns |username| unless it is empty. For an empty |username| returns a
// localised string saying this username is empty. Use this for displaying the
// usernames to the user.
base::string16 ReplaceEmptyUsername(const base::string16& username) {
if (username.empty())
return l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN);
return username;
}
// Returns the prettified version of |signon_realm| to be displayed on the UI.
base::string16 GetHumanReadableRealm(const std::string& signon_realm) {
// For Android application realms, remove the hash component. Otherwise, make
// no changes.
FacetURI maybe_facet_uri(FacetURI::FromPotentiallyInvalidSpec(signon_realm));
if (maybe_facet_uri.IsValidAndroidFacetURI())
return base::UTF8ToUTF16("android://" +
maybe_facet_uri.android_package_name() + "/");
return base::UTF8ToUTF16(signon_realm);
}
// If |field_suggestion| matches |field_content|, creates a Suggestion out of it
// and appends to |suggestions|.
void AppendSuggestionIfMatching(
const base::string16& field_suggestion,
const base::string16& field_contents,
const std::string& signon_realm,
bool show_all,
std::vector<autofill::Suggestion>* suggestions) {
base::string16 lower_suggestion = base::i18n::ToLower(field_suggestion);
base::string16 lower_contents = base::i18n::ToLower(field_contents);
bool prefix_matched_suggestion =
show_all || base::StartsWith(lower_suggestion, lower_contents,
base::CompareCase::SENSITIVE);
if (prefix_matched_suggestion ||
autofill::FieldIsSuggestionSubstringStartingOnTokenBoundary(
lower_suggestion, lower_contents, true)) {
autofill::Suggestion suggestion(ReplaceEmptyUsername(field_suggestion));
suggestion.label = GetHumanReadableRealm(signon_realm);
suggestion.frontend_id = autofill::POPUP_ITEM_ID_PASSWORD_ENTRY;
suggestion.match = prefix_matched_suggestion
? autofill::Suggestion::PREFIX_MATCH
: autofill::Suggestion::SUBSTRING_MATCH;
suggestions->push_back(suggestion);
}
}
// This function attempts to fill |suggestions| and |realms| form |fill_data|
// based on |current_username|. Unless |show_all| is true, it only picks
// suggestions where the username has |current_username| as a prefix.
void GetSuggestions(const autofill::PasswordFormFillData& fill_data,
const base::string16& current_username,
std::vector<autofill::Suggestion>* suggestions,
bool show_all) {
AppendSuggestionIfMatching(fill_data.username_field.value, current_username,
fill_data.preferred_realm, show_all, suggestions);
for (const auto& login : fill_data.additional_logins) {
AppendSuggestionIfMatching(login.first, current_username,
login.second.realm, show_all, suggestions);
}
for (const auto& usernames : fill_data.other_possible_usernames) {
for (size_t i = 0; i < usernames.second.size(); ++i) {
AppendSuggestionIfMatching(usernames.second[i], current_username,
usernames.first.realm, show_all, suggestions);
}
}
// Prefix matches should precede other token matches.
if (autofill::IsFeatureSubstringMatchEnabled()) {
std::sort(suggestions->begin(), suggestions->end(),
[](const autofill::Suggestion& a, const autofill::Suggestion& b) {
return a.match < b.match;
});
}
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// PasswordAutofillManager, public:
PasswordAutofillManager::PasswordAutofillManager(
PasswordManagerDriver* password_manager_driver,
autofill::AutofillClient* autofill_client)
: password_manager_driver_(password_manager_driver),
autofill_client_(autofill_client),
weak_ptr_factory_(this) {
}
PasswordAutofillManager::~PasswordAutofillManager() {
}
bool PasswordAutofillManager::FillSuggestion(int key,
const base::string16& username) {
autofill::PasswordFormFillData fill_data;
autofill::PasswordAndRealm password_and_realm;
if (FindLoginInfo(key, &fill_data) &&
GetPasswordAndRealmForUsername(
username, fill_data, &password_and_realm)) {
bool is_android_credential = FacetURI::FromPotentiallyInvalidSpec(
password_and_realm.realm).IsValidAndroidFacetURI();
metrics_util::LogFilledCredentialIsFromAndroidApp(is_android_credential);
password_manager_driver_->FillSuggestion(
username, password_and_realm.password);
return true;
}
return false;
}
bool PasswordAutofillManager::PreviewSuggestion(
int key,
const base::string16& username) {
autofill::PasswordFormFillData fill_data;
autofill::PasswordAndRealm password_and_realm;
if (FindLoginInfo(key, &fill_data) &&
GetPasswordAndRealmForUsername(
username, fill_data, &password_and_realm)) {
password_manager_driver_->PreviewSuggestion(
username, password_and_realm.password);
return true;
}
return false;
}
void PasswordAutofillManager::OnAddPasswordFormMapping(
int key,
const autofill::PasswordFormFillData& fill_data) {
if (!autofill::IsValidPasswordFormFillData(fill_data))
return;
login_to_password_info_[key] = fill_data;
}
void PasswordAutofillManager::OnShowPasswordSuggestions(
int key,
base::i18n::TextDirection text_direction,
const base::string16& typed_username,
int options,
const gfx::RectF& bounds) {
std::vector<autofill::Suggestion> suggestions;
LoginToPasswordInfoMap::const_iterator fill_data_it =
login_to_password_info_.find(key);
if (fill_data_it == login_to_password_info_.end()) {
// Probably a compromised renderer.
NOTREACHED();
return;
}
GetSuggestions(fill_data_it->second, typed_username, &suggestions,
options & autofill::SHOW_ALL);
form_data_key_ = key;
if (suggestions.empty()) {
autofill_client_->HideAutofillPopup();
return;
}
if (options & autofill::IS_PASSWORD_FIELD) {
autofill::Suggestion password_field_suggestions(l10n_util::GetStringUTF16(
IDS_AUTOFILL_PASSWORD_FIELD_SUGGESTIONS_TITLE));
password_field_suggestions.frontend_id = autofill::POPUP_ITEM_ID_TITLE;
suggestions.insert(suggestions.begin(), password_field_suggestions);
}
autofill_client_->ShowAutofillPopup(bounds,
text_direction,
suggestions,
weak_ptr_factory_.GetWeakPtr());
}
void PasswordAutofillManager::DidNavigateMainFrame() {
login_to_password_info_.clear();
}
bool PasswordAutofillManager::FillSuggestionForTest(
int key,
const base::string16& username) {
return FillSuggestion(key, username);
}
bool PasswordAutofillManager::PreviewSuggestionForTest(
int key,
const base::string16& username) {
return PreviewSuggestion(key, username);
}
void PasswordAutofillManager::OnPopupShown() {
}
void PasswordAutofillManager::OnPopupHidden() {
}
void PasswordAutofillManager::DidSelectSuggestion(const base::string16& value,
int identifier) {
ClearPreviewedForm();
bool success = PreviewSuggestion(form_data_key_, value);
DCHECK(success);
}
void PasswordAutofillManager::DidAcceptSuggestion(const base::string16& value,
int identifier,
int position) {
bool success = FillSuggestion(form_data_key_, value);
DCHECK(success);
autofill_client_->HideAutofillPopup();
}
bool PasswordAutofillManager::GetDeletionConfirmationText(
const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) {
return false;
}
bool PasswordAutofillManager::RemoveSuggestion(const base::string16& value,
int identifier) {
// Password suggestions cannot be deleted this way.
// See http://crbug.com/329038#c15
return false;
}
void PasswordAutofillManager::ClearPreviewedForm() {
password_manager_driver_->ClearPreviewedForm();
}
////////////////////////////////////////////////////////////////////////////////
// PasswordAutofillManager, private:
bool PasswordAutofillManager::GetPasswordAndRealmForUsername(
const base::string16& current_username,
const autofill::PasswordFormFillData& fill_data,
autofill::PasswordAndRealm* password_and_realm) {
// TODO(dubroy): When password access requires some kind of authentication
// (e.g. Keychain access on Mac OS), use |password_manager_client_| here to
// fetch the actual password. See crbug.com/178358 for more context.
// Look for any suitable matches to current field text.
if (CompareUsernameSuggestion(fill_data.username_field.value,
current_username)) {
password_and_realm->password = fill_data.password_field.value;
password_and_realm->realm = fill_data.preferred_realm;
return true;
}
// Scan additional logins for a match.
for (autofill::PasswordFormFillData::LoginCollection::const_iterator iter =
fill_data.additional_logins.begin();
iter != fill_data.additional_logins.end(); ++iter) {
if (CompareUsernameSuggestion(iter->first, current_username)) {
*password_and_realm = iter->second;
return true;
}
}
for (autofill::PasswordFormFillData::UsernamesCollection::const_iterator
usernames_iter = fill_data.other_possible_usernames.begin();
usernames_iter != fill_data.other_possible_usernames.end();
++usernames_iter) {
for (size_t i = 0; i < usernames_iter->second.size(); ++i) {
if (CompareUsernameSuggestion(usernames_iter->second[i],
current_username)) {
password_and_realm->password = usernames_iter->first.password;
password_and_realm->realm = usernames_iter->first.realm;
return true;
}
}
}
return false;
}
bool PasswordAutofillManager::FindLoginInfo(
int key,
autofill::PasswordFormFillData* found_password) {
LoginToPasswordInfoMap::iterator iter = login_to_password_info_.find(key);
if (iter == login_to_password_info_.end())
return false;
*found_password = iter->second;
return true;
}
} // namespace password_manager