Commit b95ffc80 authored by Mohamed Amir Yosef's avatar Mohamed Amir Yosef Committed by Commit Bot

[Passwords] MultiStoreFormFetcher sorts matches per date_last_used

Change-Id: Ib4b42e493f6799681798c39165424111977fb21a
Bug: 1002000
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1800869
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: default avatarVadym Doroshenko <dvadym@chromium.org>
Cr-Commit-Position: refs/heads/master@{#697600}
parent 9067d54d
...@@ -66,9 +66,10 @@ const PasswordForm* FakeFormFetcher::GetPreferredMatch() const { ...@@ -66,9 +66,10 @@ const PasswordForm* FakeFormFetcher::GetPreferredMatch() const {
void FakeFormFetcher::SetNonFederated( void FakeFormFetcher::SetNonFederated(
const std::vector<const PasswordForm*>& non_federated) { const std::vector<const PasswordForm*>& non_federated) {
non_federated_ = non_federated; non_federated_ = non_federated;
password_manager_util::FindBestMatches(non_federated_, scheme_, password_manager_util::FindBestMatches(
&non_federated_same_scheme_, non_federated_, scheme_,
&best_matches_, &preferred_match_); /*sort_matches_by_date_last_used=*/false, &non_federated_same_scheme_,
&best_matches_, &preferred_match_);
} }
void FakeFormFetcher::SetBlacklisted( void FakeFormFetcher::SetBlacklisted(
......
...@@ -219,8 +219,8 @@ std::unique_ptr<FormFetcher> FormFetcherImpl::Clone() { ...@@ -219,8 +219,8 @@ std::unique_ptr<FormFetcher> FormFetcherImpl::Clone() {
result->blacklisted_ = MakeCopies(blacklisted_); result->blacklisted_ = MakeCopies(blacklisted_);
password_manager_util::FindBestMatches( password_manager_util::FindBestMatches(
MakeWeakCopies(result->non_federated_), form_digest_.scheme, MakeWeakCopies(result->non_federated_), form_digest_.scheme,
&result->non_federated_same_scheme_, &result->best_matches_, sort_matches_by_date_last_used_, &result->non_federated_same_scheme_,
&result->preferred_match_); &result->best_matches_, &result->preferred_match_);
result->interactions_stats_ = this->interactions_stats_; result->interactions_stats_ = this->interactions_stats_;
result->state_ = this->state_; result->state_ = this->state_;
...@@ -237,7 +237,8 @@ void FormFetcherImpl::ProcessPasswordStoreResults( ...@@ -237,7 +237,8 @@ void FormFetcherImpl::ProcessPasswordStoreResults(
password_manager_util::FindBestMatches( password_manager_util::FindBestMatches(
MakeWeakCopies(non_federated_), form_digest_.scheme, MakeWeakCopies(non_federated_), form_digest_.scheme,
&non_federated_same_scheme_, &best_matches_, &preferred_match_); sort_matches_by_date_last_used_, &non_federated_same_scheme_,
&best_matches_, &preferred_match_);
for (auto* consumer : consumers_) for (auto* consumer : consumers_)
consumer->OnFetchCompleted(); consumer->OnFetchCompleted();
......
...@@ -88,6 +88,9 @@ class FormFetcherImpl : public FormFetcher, ...@@ -88,6 +88,9 @@ class FormFetcherImpl : public FormFetcher,
// password store returning results in the meantime. // password store returning results in the meantime.
bool need_to_refetch_ = false; bool need_to_refetch_ = false;
// If false, matches are sorted using the "preferred" field.
bool sort_matches_by_date_last_used_ = false;
// Processes password form results and forwards them to the |consumers_|. // Processes password form results and forwards them to the |consumers_|.
void ProcessPasswordStoreResults( void ProcessPasswordStoreResults(
std::vector<std::unique_ptr<autofill::PasswordForm>> results); std::vector<std::unique_ptr<autofill::PasswordForm>> results);
......
...@@ -20,7 +20,9 @@ MultiStoreFormFetcher::MultiStoreFormFetcher( ...@@ -20,7 +20,9 @@ MultiStoreFormFetcher::MultiStoreFormFetcher(
PasswordStore::FormDigest form_digest, PasswordStore::FormDigest form_digest,
const PasswordManagerClient* client, const PasswordManagerClient* client,
bool should_migrate_http_passwords) bool should_migrate_http_passwords)
: FormFetcherImpl(form_digest, client, should_migrate_http_passwords) {} : FormFetcherImpl(form_digest, client, should_migrate_http_passwords) {
sort_matches_by_date_last_used_ = true;
}
MultiStoreFormFetcher::~MultiStoreFormFetcher() = default; MultiStoreFormFetcher::~MultiStoreFormFetcher() = default;
...@@ -83,8 +85,6 @@ void MultiStoreFormFetcher::OnGetPasswordStoreResults( ...@@ -83,8 +85,6 @@ void MultiStoreFormFetcher::OnGetPasswordStoreResults(
// TODO(crbug.com/1002000): implement password store migration. // TODO(crbug.com/1002000): implement password store migration.
// TODO(crbug.com/1002000): implement sorting based on "last_time_used"
ProcessPasswordStoreResults(std::move(partial_results_)); ProcessPasswordStoreResults(std::move(partial_results_));
} }
......
...@@ -64,26 +64,31 @@ class FakePasswordManagerClient : public StubPasswordManagerClient { ...@@ -64,26 +64,31 @@ class FakePasswordManagerClient : public StubPasswordManagerClient {
PasswordForm CreateHTMLForm(const std::string& origin_url, PasswordForm CreateHTMLForm(const std::string& origin_url,
const std::string& username_value, const std::string& username_value,
const std::string& password_value) { const std::string& password_value,
base::Time date_last_used) {
PasswordForm form; PasswordForm form;
form.scheme = PasswordForm::Scheme::kHtml; form.scheme = PasswordForm::Scheme::kHtml;
form.origin = GURL(origin_url); form.origin = GURL(origin_url);
form.signon_realm = origin_url; form.signon_realm = origin_url;
form.username_value = ASCIIToUTF16(username_value); form.username_value = ASCIIToUTF16(username_value);
form.password_value = ASCIIToUTF16(password_value); form.password_value = ASCIIToUTF16(password_value);
form.date_last_used = date_last_used;
return form; return form;
} }
// Creates a dummy non-federated form with some basic arbitrary values. // Creates a dummy non-federated form with some basic arbitrary values.
PasswordForm CreateNonFederated(const std::string& username_value) { PasswordForm CreateNonFederated(const std::string& username_value,
PasswordForm form = CreateHTMLForm(kTestHttpsURL, username_value, "password"); base::Time date_last_used) {
PasswordForm form =
CreateHTMLForm(kTestHttpsURL, username_value, "password", date_last_used);
form.action = GURL(kTestHttpsActionURL); form.action = GURL(kTestHttpsActionURL);
return form; return form;
} }
// Creates a dummy federated form with some basic arbitrary values. // Creates a dummy federated form with some basic arbitrary values.
PasswordForm CreateFederated(const std::string& username_value) { PasswordForm CreateFederated(const std::string& username_value,
PasswordForm form = CreateNonFederated(username_value); base::Time date_last_used) {
PasswordForm form = CreateNonFederated(username_value, date_last_used);
form.signon_realm = kTestFederatedRealm; form.signon_realm = kTestFederatedRealm;
form.password_value.clear(); form.password_value.clear();
form.federation_origin = url::Origin::Create(GURL(kTestFederationURL)); form.federation_origin = url::Origin::Create(GURL(kTestFederationURL));
...@@ -91,9 +96,11 @@ PasswordForm CreateFederated(const std::string& username_value) { ...@@ -91,9 +96,11 @@ PasswordForm CreateFederated(const std::string& username_value) {
} }
// Creates an Android federated credential. // Creates an Android federated credential.
PasswordForm CreateAndroidFederated(const std::string& username_value) { PasswordForm CreateAndroidFederated(const std::string& username_value,
base::Time date_last_used) {
PasswordForm form = PasswordForm form =
CreateHTMLForm("android://hash@com.example.android/", username_value, ""); CreateHTMLForm("android://hash@com.example.android/", username_value,
/*password_value=*/"", date_last_used);
form.federation_origin = url::Origin::Create(GURL(kTestFederationURL)); form.federation_origin = url::Origin::Create(GURL(kTestFederationURL));
form.is_affiliation_based_match = true; form.is_affiliation_based_match = true;
return form; return form;
...@@ -101,7 +108,9 @@ PasswordForm CreateAndroidFederated(const std::string& username_value) { ...@@ -101,7 +108,9 @@ PasswordForm CreateAndroidFederated(const std::string& username_value) {
// Creates a dummy blacklisted form. // Creates a dummy blacklisted form.
PasswordForm CreateBlacklisted() { PasswordForm CreateBlacklisted() {
PasswordForm form = CreateHTMLForm(kTestHttpsURL, "", ""); PasswordForm form = CreateHTMLForm(kTestHttpsURL, /*username_value=*/"",
/*password_value=*/"",
/*date_last_used=*/base::Time::Now());
form.blacklisted_by_user = true; form.blacklisted_by_user = true;
return form; return form;
} }
...@@ -190,13 +199,17 @@ TEST_F(MultiStoreFormFetcherTest, Empty) { ...@@ -190,13 +199,17 @@ TEST_F(MultiStoreFormFetcherTest, Empty) {
// Check that results from both stores are merged. // Check that results from both stores are merged.
TEST_F(MultiStoreFormFetcherTest, MergeFromBothStores) { TEST_F(MultiStoreFormFetcherTest, MergeFromBothStores) {
const base::Time kLastUsedNow = base::Time::Now();
const base::Time kLastUsedYesterday =
kLastUsedNow - base::TimeDelta::FromDays(1);
Fetch(); Fetch();
PasswordForm federated1 = CreateFederated("user"); PasswordForm federated1 = CreateFederated("user", kLastUsedNow);
PasswordForm federated2 = CreateFederated("user_B"); PasswordForm federated2 = CreateFederated("user_B", kLastUsedNow);
PasswordForm federated3 = CreateAndroidFederated("user_B"); PasswordForm federated3 = CreateAndroidFederated("user_B", kLastUsedNow);
PasswordForm non_federated1 = CreateNonFederated("user"); PasswordForm non_federated1 = CreateNonFederated("user", kLastUsedYesterday);
PasswordForm non_federated2 = CreateNonFederated("user_C"); PasswordForm non_federated2 =
PasswordForm non_federated3 = CreateNonFederated("user_D"); CreateNonFederated("user_C", kLastUsedYesterday);
PasswordForm non_federated3 = CreateNonFederated("user_D", kLastUsedNow);
PasswordForm blacklisted = CreateBlacklisted(); PasswordForm blacklisted = CreateBlacklisted();
form_fetcher_->AddConsumer(&consumer_); form_fetcher_->AddConsumer(&consumer_);
...@@ -233,6 +246,7 @@ TEST_F(MultiStoreFormFetcherTest, MergeFromBothStores) { ...@@ -233,6 +246,7 @@ TEST_F(MultiStoreFormFetcherTest, MergeFromBothStores) {
Pointee(federated3))); Pointee(federated3)));
EXPECT_THAT(form_fetcher_->GetBlacklistedMatches(), EXPECT_THAT(form_fetcher_->GetBlacklistedMatches(),
UnorderedElementsAre(Pointee(blacklisted))); UnorderedElementsAre(Pointee(blacklisted)));
EXPECT_THAT(form_fetcher_->GetPreferredMatch(), Pointee(non_federated3));
} }
} // namespace password_manager } // namespace password_manager
...@@ -49,6 +49,17 @@ bool IsBetterMatch(const PasswordForm* lhs, const PasswordForm* rhs) { ...@@ -49,6 +49,17 @@ bool IsBetterMatch(const PasswordForm* lhs, const PasswordForm* rhs) {
std::make_pair(!rhs->is_public_suffix_match, rhs->preferred); std::make_pair(!rhs->is_public_suffix_match, rhs->preferred);
} }
// Return true if 1.|lhs| is non-PSL match, |rhs| is PSL match or 2.|lhs| and
// |rhs| have the same value of |is_public_suffix_match|, and |lhs| is more
// recently used than |rhs|.
// TODO(crbug.com/1002000): Rename to IsBetterMatch when migration to
// date_last_used is done.
bool IsBetterMatchUsingLastUsed(const PasswordForm* lhs,
const PasswordForm* rhs) {
return std::make_pair(!lhs->is_public_suffix_match, lhs->date_last_used) >
std::make_pair(!rhs->is_public_suffix_match, rhs->date_last_used);
}
} // namespace } // namespace
// Update |credential| to reflect usage. // Update |credential| to reflect usage.
...@@ -237,6 +248,7 @@ base::StringPiece GetSignonRealmWithProtocolExcluded(const PasswordForm& form) { ...@@ -237,6 +248,7 @@ base::StringPiece GetSignonRealmWithProtocolExcluded(const PasswordForm& form) {
void FindBestMatches( void FindBestMatches(
const std::vector<const PasswordForm*>& non_federated_matches, const std::vector<const PasswordForm*>& non_federated_matches,
PasswordForm::Scheme scheme, PasswordForm::Scheme scheme,
bool sort_matches_by_date_last_used,
std::vector<const autofill::PasswordForm*>* non_federated_same_scheme, std::vector<const autofill::PasswordForm*>* non_federated_same_scheme,
std::map<base::string16, const PasswordForm*>* best_matches, std::map<base::string16, const PasswordForm*>* best_matches,
const PasswordForm** preferred_match) { const PasswordForm** preferred_match) {
...@@ -259,9 +271,12 @@ void FindBestMatches( ...@@ -259,9 +271,12 @@ void FindBestMatches(
if (non_federated_same_scheme->empty()) if (non_federated_same_scheme->empty())
return; return;
// Sort matches using IsBetterMatch predicate. // Sort matches using IsBetterMatchUsingLastUsed or IsBetterMatch predicate.
std::sort(non_federated_same_scheme->begin(), std::sort(non_federated_same_scheme->begin(),
non_federated_same_scheme->end(), IsBetterMatch); non_federated_same_scheme->end(),
sort_matches_by_date_last_used ? IsBetterMatchUsingLastUsed
: IsBetterMatch);
for (const auto* match : *non_federated_same_scheme) { for (const auto* match : *non_federated_same_scheme) {
const base::string16& username = match->username_value; const base::string16& username = match->username_value;
// The first match for |username| in the sorted array is best match. // The first match for |username| in the sorted array is best match.
......
...@@ -122,6 +122,7 @@ base::StringPiece GetSignonRealmWithProtocolExcluded( ...@@ -122,6 +122,7 @@ base::StringPiece GetSignonRealmWithProtocolExcluded(
void FindBestMatches( void FindBestMatches(
const std::vector<const autofill::PasswordForm*>& non_federated_matches, const std::vector<const autofill::PasswordForm*>& non_federated_matches,
autofill::PasswordForm::Scheme scheme, autofill::PasswordForm::Scheme scheme,
bool sort_matches_by_date_last_used,
std::vector<const autofill::PasswordForm*>* non_federated_same_scheme, std::vector<const autofill::PasswordForm*>* non_federated_same_scheme,
std::map<base::string16, const autofill::PasswordForm*>* best_matches, std::map<base::string16, const autofill::PasswordForm*>* best_matches,
const autofill::PasswordForm** preferred_match); const autofill::PasswordForm** preferred_match);
......
...@@ -201,8 +201,164 @@ TEST(PasswordManagerUtil, FindBestMatches) { ...@@ -201,8 +201,164 @@ TEST(PasswordManagerUtil, FindBestMatches) {
const PasswordForm* preferred_match = nullptr; const PasswordForm* preferred_match = nullptr;
std::vector<const PasswordForm*> same_scheme_matches; std::vector<const PasswordForm*> same_scheme_matches;
FindBestMatches(matches, PasswordForm::Scheme::kHtml, &same_scheme_matches, FindBestMatches(matches, PasswordForm::Scheme::kHtml,
&best_matches, &preferred_match); /*sort_matches_by_date_last_used=*/false,
&same_scheme_matches, &best_matches, &preferred_match);
if (test_case.expected_preferred_match_index == kNotFound) {
// Case of empty |matches|.
EXPECT_FALSE(preferred_match);
EXPECT_TRUE(best_matches.empty());
} else {
// Check |preferred_match|.
EXPECT_EQ(matches[test_case.expected_preferred_match_index],
preferred_match);
// Check best matches.
ASSERT_EQ(test_case.expected_best_matches_indices.size(),
best_matches.size());
for (const auto& username_match : best_matches) {
std::string username = base::UTF16ToUTF8(username_match.first);
ASSERT_NE(test_case.expected_best_matches_indices.end(),
test_case.expected_best_matches_indices.find(username));
size_t expected_index =
test_case.expected_best_matches_indices.at(username);
size_t actual_index = std::distance(
matches.begin(),
std::find(matches.begin(), matches.end(), username_match.second));
EXPECT_EQ(expected_index, actual_index);
}
}
}
}
TEST(PasswordManagerUtil, FindBestMatchesByUsageTime) {
const base::Time kNow = base::Time::Now();
const base::Time kYesterday = kNow - base::TimeDelta::FromDays(1);
const base::Time k2DaysAgo = kNow - base::TimeDelta::FromDays(2);
const int kNotFound = -1;
struct TestMatch {
bool is_psl_match;
bool preferred;
base::Time date_last_used;
std::string username;
};
struct TestCase {
const char* description;
std::vector<TestMatch> matches;
int expected_preferred_match_index;
std::map<std::string, size_t> expected_best_matches_indices;
} test_cases[] = {
{"Empty matches", {}, kNotFound, {}},
{"1 preferred non-psl match",
{{.is_psl_match = false,
.preferred = true,
.date_last_used = kNow,
.username = "u"}},
0,
{{"u", 0}}},
{"1 non-preferred psl match",
{{.is_psl_match = true,
.preferred = false,
.date_last_used = kNow,
.username = "u"}},
0,
{{"u", 0}}},
{"2 matches with the same username",
{{.is_psl_match = false,
.preferred = false,
.date_last_used = kNow,
.username = "u"},
{.is_psl_match = false,
.preferred = true,
.date_last_used = kYesterday,
.username = "u"}},
0,
{{"u", 0}}},
{"2 matches with different usernames, most recently used taken",
{{.is_psl_match = false,
.preferred = false,
.date_last_used = kNow,
.username = "u1"},
{.is_psl_match = false,
.preferred = true,
.date_last_used = kYesterday,
.username = "u2"}},
0,
{{"u1", 0}, {"u2", 1}}},
{"2 matches with different usernames, non-psl much taken",
{{.is_psl_match = false,
.preferred = false,
.date_last_used = kYesterday,
.username = "u1"},
{.is_psl_match = true,
.preferred = true,
.date_last_used = kNow,
.username = "u2"}},
0,
{{"u1", 0}, {"u2", 1}}},
{"8 matches, 3 usernames",
{{.is_psl_match = false,
.preferred = false,
.date_last_used = kYesterday,
.username = "u2"},
{.is_psl_match = true,
.preferred = false,
.date_last_used = kYesterday,
.username = "u3"},
{.is_psl_match = true,
.preferred = false,
.date_last_used = kYesterday,
.username = "u1"},
{.is_psl_match = false,
.preferred = true,
.date_last_used = k2DaysAgo,
.username = "u3"},
{.is_psl_match = true,
.preferred = false,
.date_last_used = kNow,
.username = "u1"},
{.is_psl_match = false,
.preferred = false,
.date_last_used = kNow,
.username = "u2"},
{.is_psl_match = true,
.preferred = true,
.date_last_used = kYesterday,
.username = "u3"},
{.is_psl_match = false,
.preferred = false,
.date_last_used = k2DaysAgo,
.username = "u1"}},
5,
{{"u1", 7}, {"u2", 5}, {"u3", 3}}},
};
for (const TestCase& test_case : test_cases) {
SCOPED_TRACE(testing::Message("Test description: ")
<< test_case.description);
// Convert TestMatch to PasswordForm.
std::vector<PasswordForm> owning_matches;
for (const TestMatch& match : test_case.matches) {
PasswordForm form;
form.is_public_suffix_match = match.is_psl_match;
form.preferred = match.preferred;
form.date_last_used = match.date_last_used;
form.username_value = base::ASCIIToUTF16(match.username);
owning_matches.push_back(form);
}
std::vector<const PasswordForm*> matches;
for (const PasswordForm& match : owning_matches)
matches.push_back(&match);
std::map<base::string16, const PasswordForm*> best_matches;
const PasswordForm* preferred_match = nullptr;
std::vector<const PasswordForm*> same_scheme_matches;
FindBestMatches(matches, PasswordForm::Scheme::kHtml,
/*sort_matches_by_date_last_used=*/true,
&same_scheme_matches, &best_matches, &preferred_match);
if (test_case.expected_preferred_match_index == kNotFound) { if (test_case.expected_preferred_match_index == kNotFound) {
// Case of empty |matches|. // Case of empty |matches|.
......
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