Commit 86eff203 authored by vabr@chromium.org's avatar vabr@chromium.org

Password manager: Gnome support for Public Suffix List matching

This CL:

* Enables Public Suffix List (PSL) general utilities on Linux.
* Enables PSL flag for Linux.
* Adds Gnome Keyring support for searching and filtering multiple origins, including UMA metrics and a unit test.

BUG=324291

Review URL: https://codereview.chromium.org/123223003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@244286 0039d316-1c4b-4281-b951-d872f2087c98
parent 75404f6f
......@@ -1287,7 +1287,7 @@ const Experiment kExperiments[] = {
"password-autofill-public-suffix-domain-matching",
IDS_FLAGS_PASSWORD_AUTOFILL_PUBLIC_SUFFIX_DOMAIN_MATCHING_NAME,
IDS_FLAGS_PASSWORD_AUTOFILL_PUBLIC_SUFFIX_DOMAIN_MATCHING_DESCRIPTION,
kOsAndroid | kOsWin,
kOsAndroid | kOsWin | kOsLinux,
ENABLE_DISABLE_VALUE_TYPE(
switches::kEnablePasswordAutofillPublicSuffixDomainMatching,
switches::kDisablePasswordAutofillPublicSuffixDomainMatching)
......
......@@ -13,6 +13,7 @@
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
......@@ -20,6 +21,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/time/time.h"
#include "chrome/browser/password_manager/psl_matching_helper.h"
#include "components/autofill/core/common/password_form.h"
#include "content/public/browser/browser_thread.h"
......@@ -137,9 +139,15 @@ PasswordForm* FormFromAttributes(GnomeKeyringAttributeList* attrs) {
// Parse all the results from the given GList into a PasswordFormList, and free
// the GList. PasswordForms are allocated on the heap, and should be deleted by
// the consumer.
// the consumer. If not empty, |filter_by_signon_realm| is used to filter out
// results -- only credentials with signon realms passing the PSL matching
// (done by |helper|) against |filter_by_signon_realm| will be kept.
void ConvertFormList(GList* found,
const std::string& filter_by_signon_realm,
const PSLMatchingHelper& helper,
NativeBackendGnome::PasswordFormList* forms) {
PSLMatchingHelper::PSLDomainMatchMetric psl_domain_match_metric =
PSLMatchingHelper::PSL_DOMAIN_MATCH_NONE;
for (GList* element = g_list_first(found); element != NULL;
element = g_list_next(element)) {
GnomeKeyringFound* data = static_cast<GnomeKeyringFound*>(element->data);
......@@ -147,6 +155,16 @@ void ConvertFormList(GList* found,
PasswordForm* form = FormFromAttributes(attrs);
if (form) {
if (!filter_by_signon_realm.empty() &&
form->signon_realm != filter_by_signon_realm) {
// This is not an exact match, we try PSL matching.
if (!(PSLMatchingHelper::IsPublicSuffixDomainMatch(
filter_by_signon_realm, form->signon_realm))) {
continue;
}
psl_domain_match_metric = PSLMatchingHelper::PSL_DOMAIN_MATCH_FOUND;
form->original_signon_realm = form->signon_realm;
}
if (data->secret) {
form->password_value = UTF8ToUTF16(data->secret);
} else {
......@@ -157,6 +175,14 @@ void ConvertFormList(GList* found,
LOG(WARNING) << "Could not initialize PasswordForm from attributes!";
}
}
if (!filter_by_signon_realm.empty()) {
UMA_HISTOGRAM_ENUMERATION(
"PasswordManager.PslDomainMatchTriggering",
helper.IsMatchingEnabled()
? psl_domain_match_metric
: PSLMatchingHelper::PSL_DOMAIN_MATCH_DISABLED,
PSLMatchingHelper::PSL_DOMAIN_MATCH_COUNT);
}
}
// Schema is analagous to the fields in PasswordForm.
......@@ -250,6 +276,12 @@ class GKRMethod : public GnomeKeyringLoader {
base::WaitableEvent event_;
GnomeKeyringResult result_;
NativeBackendGnome::PasswordFormList forms_;
// Two additional arguments to OnOperationGetList:
// If the credential search is related to a particular form,
// |original_signon_realm_| contains the signon realm of that form. It is used
// to filter the relevant results out of all the found ones.
std::string original_signon_realm_;
const PSLMatchingHelper helper_;
};
void GKRMethod::AddLogin(const PasswordForm& form, const char* app_string) {
......@@ -286,6 +318,7 @@ void GKRMethod::AddLogin(const PasswordForm& form, const char* app_string) {
void GKRMethod::AddLoginSearch(const PasswordForm& form,
const char* app_string) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
original_signon_realm_ = form.signon_realm;
// Search GNOME Keyring for matching passwords to update.
ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
AppendString(&attrs, "origin_url", form.origin.spec());
......@@ -305,6 +338,7 @@ void GKRMethod::AddLoginSearch(const PasswordForm& form,
void GKRMethod::UpdateLoginSearch(const PasswordForm& form,
const char* app_string) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
original_signon_realm_ = form.signon_realm;
// Search GNOME Keyring for matching passwords to update.
ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
AppendString(&attrs, "origin_url", form.origin.spec());
......@@ -341,9 +375,14 @@ void GKRMethod::RemoveLogin(const PasswordForm& form, const char* app_string) {
void GKRMethod::GetLogins(const PasswordForm& form, const char* app_string) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
original_signon_realm_ = form.signon_realm;
// Search GNOME Keyring for matching passwords.
ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
AppendString(&attrs, "signon_realm", form.signon_realm);
if (!helper_.ShouldPSLDomainMatchingApply(
PSLMatchingHelper::GetRegistryControlledDomain(
GURL(form.signon_realm)))) {
AppendString(&attrs, "signon_realm", form.signon_realm);
}
AppendString(&attrs, "application", app_string);
gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET,
attrs.get(),
......@@ -355,6 +394,7 @@ void GKRMethod::GetLogins(const PasswordForm& form, const char* app_string) {
void GKRMethod::GetLoginsList(uint32_t blacklisted_by_user,
const char* app_string) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
original_signon_realm_.clear();
// Search GNOME Keyring for matching passwords.
ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
AppendUint32(&attrs, "blacklisted_by_user", blacklisted_by_user);
......@@ -368,6 +408,7 @@ void GKRMethod::GetLoginsList(uint32_t blacklisted_by_user,
void GKRMethod::GetAllLogins(const char* app_string) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
original_signon_realm_.clear();
// We need to search for something, otherwise we get no results - so
// we search for the fixed application string.
ScopedAttributeList attrs(gnome_keyring_attribute_list_new());
......@@ -434,7 +475,9 @@ void GKRMethod::OnOperationGetList(GnomeKeyringResult result, GList* list,
method->result_ = result;
method->forms_.clear();
// |list| will be freed after this callback returns, so convert it now.
ConvertFormList(list, &method->forms_);
ConvertFormList(
list, method->original_signon_realm_, method->helper_, &method->forms_);
method->original_signon_realm_.clear();
method->event_.Signal();
}
......
......@@ -12,6 +12,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/password_manager/native_backend_gnome_x.h"
#include "chrome/browser/password_manager/psl_matching_helper.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/autofill/core/common/password_form.h"
......@@ -298,7 +299,16 @@ class NativeBackendGnomeTest : public testing::Test {
form_google_.password_element = UTF8ToUTF16("pass");
form_google_.password_value = UTF8ToUTF16("seekrit");
form_google_.submit_element = UTF8ToUTF16("submit");
form_google_.signon_realm = "Google";
form_google_.signon_realm = "http://www.google.com/";
form_facebook_.origin = GURL("http://www.facebook.com/");
form_facebook_.action = GURL("http://www.facebook.com/login");
form_facebook_.username_element = UTF8ToUTF16("user");
form_facebook_.username_value = UTF8ToUTF16("a");
form_facebook_.password_element = UTF8ToUTF16("password");
form_facebook_.password_value = UTF8ToUTF16("b");
form_facebook_.submit_element = UTF8ToUTF16("submit");
form_facebook_.signon_realm = "http://www.facebook.com/";
form_isc_.origin = GURL("http://www.isc.org/");
form_isc_.action = GURL("http://www.isc.org/auth");
......@@ -307,7 +317,7 @@ class NativeBackendGnomeTest : public testing::Test {
form_isc_.password_element = UTF8ToUTF16("passwd");
form_isc_.password_value = UTF8ToUTF16("ihazabukkit");
form_isc_.submit_element = UTF8ToUTF16("login");
form_isc_.signon_realm = "ISC";
form_isc_.signon_realm = "http://www.isc.org/";
}
virtual void TearDown() {
......@@ -383,6 +393,51 @@ class NativeBackendGnomeTest : public testing::Test {
CheckStringAttribute(item, "application", app_string);
}
// Checks (using EXPECT_* macros), that |credentials| are accessible for
// filling in for a page with |origin| iff
// |should_credential_be_available_to_url| is true.
void CheckCredentialAvailability(const PasswordForm& credentials,
const std::string& url,
bool should_credential_be_available_to_url) {
PSLMatchingHelper helper;
ASSERT_TRUE(helper.IsMatchingEnabled())
<< "PSL matching needs to be enabled.";
NativeBackendGnome backend(321, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(
BrowserThread::DB,
FROM_HERE,
base::Bind(base::IgnoreResult(&NativeBackendGnome::AddLogin),
base::Unretained(&backend),
credentials));
PasswordForm target_form;
target_form.origin = GURL(url);
target_form.signon_realm = url;
std::vector<PasswordForm*> form_list;
BrowserThread::PostTask(
BrowserThread::DB,
FROM_HERE,
base::Bind(base::IgnoreResult(&NativeBackendGnome::GetLogins),
base::Unretained(&backend),
target_form,
&form_list));
RunBothThreads();
EXPECT_EQ(1u, mock_keyring_items.size());
if (mock_keyring_items.size() > 0)
CheckMockKeyringItem(&mock_keyring_items[0], credentials, "chrome-321");
if (should_credential_be_available_to_url)
EXPECT_EQ(1u, form_list.size());
else
EXPECT_EQ(0u, form_list.size());
STLDeleteElements(&form_list);
}
base::MessageLoopForUI message_loop_;
content::TestBrowserThread ui_thread_;
content::TestBrowserThread db_thread_;
......@@ -391,6 +446,7 @@ class NativeBackendGnomeTest : public testing::Test {
// Provide some test forms to avoid having to set them up in each test.
PasswordForm form_google_;
PasswordForm form_facebook_;
PasswordForm form_isc_;
};
......@@ -443,6 +499,28 @@ TEST_F(NativeBackendGnomeTest, BasicListLogins) {
CheckMockKeyringItem(&mock_keyring_items[0], form_google_, "chrome-42");
}
// Save a password for www.facebook.com and see it suggested for m.facebook.com.
TEST_F(NativeBackendGnomeTest, PSLMatchingPositive) {
CheckCredentialAvailability(form_facebook_,
"http://m.facebook.com/",
/*should_credential_be_available_to_url=*/true);
}
// Save a password for www.facebook.com and see it not suggested for
// m-facebook.com.
TEST_F(NativeBackendGnomeTest, PSLMatchingNegativeDomainMismatch) {
CheckCredentialAvailability(form_facebook_,
"http://m-facebook.com/",
/*should_credential_be_available_to_url=*/false);
}
// Test PSL matching is off for domains excluded from it.
TEST_F(NativeBackendGnomeTest, PSLMatchingDisabledDomains) {
CheckCredentialAvailability(form_google_,
"http://one.google.com/",
/*should_credential_be_available_to_url=*/false);
}
TEST_F(NativeBackendGnomeTest, BasicRemoveLogin) {
// Pretend that the migration has already taken place.
profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
......
......@@ -55,7 +55,7 @@ void PSLMatchingHelper::EnablePublicSuffixDomainMatchingForTesting() {
// static
bool PSLMatchingHelper::DeterminePSLEnabled() {
#if defined(OS_ANDROID) || defined(OS_WIN)
#if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_LINUX)
// Default choice is "enabled", so we do not need to check for
// kEnablePasswordAutofillPublicSuffixDomainMatching.
bool enabled = true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment