// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO: crbug.com/352295124 - Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include "components/password_manager/ios/account_select_fill_data.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/autofill/core/common/password_form_fill_data.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "components/password_manager/ios/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
using autofill::FieldRendererId;
using autofill::FormRendererId;
using autofill::PasswordFormFillData;
using password_manager::AccountSelectFillData;
using password_manager::FillData;
using password_manager::UsernameAndRealm;
using test_helpers::SetPasswordFormFillData;
namespace {
// Test data.
const char* kUrl = "http://example.com/";
const char* kFormNames[] = {"form_name1", "form_name2"};
const uint32_t kFormUniqueIDs[] = {0, 3};
const char* kUsernameElements[] = {"username1", "username2"};
const uint32_t kUsernameUniqueIDs[] = {1, 4};
const char* kUsernames[] = {"user0", "u_s_e_r"};
const char* kPasswordElements[] = {"password1", "password2"};
const uint32_t kPasswordUniqueIDs[] = {2, 5};
const char* kPasswords[] = {"password0", "secret"};
const char* kAdditionalUsernames[] = {"u$er2", nullptr};
const char* kAdditionalPasswords[] = {"secret", nullptr};
// Returns a field renderer ID that isn't used in any testing data, which
// represents an unexisting field renderer ID.
autofill::FieldRendererId UnexistingFieldRendererId() {
return autofill::FieldRendererId(1000);
}
// Returns form data for a single username form with credentials eligible for
// filling that has at least one non-empty username.
PasswordFormFillData EligibleSingleUsernameFormData() {
// Set fill data with 1 non-empty username and 1 empty username.
PasswordFormFillData form_data;
test_helpers::SetPasswordFormFillData(
kUrl, /*form_name=*/"", /*renderer_id=*/1, /*username_field=*/"",
/*username_field_id=*/1,
/*username_value=*/"", /*password_field=*/"", /*password_field_id=*/0,
/*password_value=*/"secret1", /*additional_username=*/"username",
/*addition_password=*/"secret2", &form_data);
return form_data;
}
// Returns form data with only empty usernames.
PasswordFormFillData FormDataWithEmptyUsernamesOnly() {
// Set fill data with 2 empty usernames.
PasswordFormFillData form_data;
test_helpers::SetPasswordFormFillData(
kUrl, /*form_name=*/"", /*renderer_id=*/1, /*username_field=*/"",
/*username_field_id=*/2,
/*username_value=*/"", /*password_field=*/"", /*password_field_id=*/3,
/*password_value=*/"secret1", /*additional_username=*/"",
/*addition_password=*/"secret2", &form_data);
return form_data;
}
// Returns form data for a single username form with credentials ineligible for
// filling that only consist of empty usernames.
PasswordFormFillData IneligibleSingleUsernameFormData() {
PasswordFormFillData form_data = FormDataWithEmptyUsernamesOnly();
// Set the default field id for the password field to make the form a single
// username form.
form_data.password_element_renderer_id = autofill::FieldRendererId(0);
return form_data;
}
class AccountSelectFillDataTest : public PlatformTest {
public:
AccountSelectFillDataTest() {
for (size_t i = 0; i < std::size(form_data_); ++i) {
SetPasswordFormFillData(
kUrl, kFormNames[i], kFormUniqueIDs[i], kUsernameElements[i],
kUsernameUniqueIDs[i], kUsernames[i], kPasswordElements[i],
kPasswordUniqueIDs[i], kPasswords[i], kAdditionalUsernames[i],
kAdditionalPasswords[i], &form_data_[i]);
}
}
protected:
PasswordFormFillData form_data_[2];
};
TEST_F(AccountSelectFillDataTest, EmptyReset) {
AccountSelectFillData account_select_fill_data;
EXPECT_TRUE(account_select_fill_data.Empty());
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
EXPECT_FALSE(account_select_fill_data.Empty());
account_select_fill_data.Reset();
EXPECT_TRUE(account_select_fill_data.Empty());
}
TEST_F(AccountSelectFillDataTest, IsSuggestionsAvailableOneForm) {
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
// Suggestions are available for the correct form and field ids.
EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
form_data_[0].form_renderer_id,
form_data_[0].username_element_renderer_id, false));
// Suggestion should be available to any password field.
EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
form_data_[0].form_renderer_id, FieldRendererId(404), true));
// Suggestions are not available for different form renderer_id.
EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
FormRendererId(404), form_data_[0].username_element_renderer_id, false));
EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
FormRendererId(404), form_data_[0].password_element_renderer_id, true));
// Suggestions are not available for different field id.
EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
form_data_[0].form_renderer_id, FieldRendererId(404), false));
}
TEST_F(AccountSelectFillDataTest, IsSuggestionsAvailableTwoForms) {
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
account_select_fill_data.Add(form_data_[1],
/*always_populate_realm=*/false);
// Suggestions are available for the correct form and field names.
EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
form_data_[0].form_renderer_id,
form_data_[0].username_element_renderer_id, false));
// Suggestions are available for the correct form and field names.
EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
form_data_[1].form_renderer_id,
form_data_[1].username_element_renderer_id, false));
// Suggestions are not available for different form id.
EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
FormRendererId(404), form_data_[0].username_element_renderer_id, false));
}
// Test that, when sign-in uff is disabled, IsSuggestionsAvailable() returns
// true on password forms when there are only empty usernames, as the
// suggestions with empty usernames still hold passwords that can be filled.
// This test makes sure that there is no regression in password filling with
// sign-in uff disabled.
TEST_F(AccountSelectFillDataTest, IsSuggestionsAvailable_EmptyUsernames) {
PasswordFormFillData form_data = FormDataWithEmptyUsernamesOnly();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
form_data.form_renderer_id, form_data.username_element_renderer_id,
false));
}
// Test that, when sign-in uff is enabled, IsSuggestionsAvailable() still
// returns true on password forms when there are only empty usernames, as the
// suggestions with empty usernames still hold passwords that can be filled.
// Sign-in uff should only target single username forms.
TEST_F(AccountSelectFillDataTest,
IsSuggestionsAvailable_EmptyUsernames_WhenSigninUffEnabled) {
PasswordFormFillData form_data = FormDataWithEmptyUsernamesOnly();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
form_data.form_renderer_id, form_data.username_element_renderer_id,
false));
}
TEST_F(AccountSelectFillDataTest,
IsSuggestionsAvailable_OnSingleUsernameForm_WhenEligible) {
PasswordFormFillData form_data = EligibleSingleUsernameFormData();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
form_data.form_renderer_id, form_data.username_element_renderer_id,
false));
}
TEST_F(AccountSelectFillDataTest,
IsSuggestionsAvailable_OnSingleUsernameForm_WhenIneligible) {
PasswordFormFillData form_data = IneligibleSingleUsernameFormData();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
form_data.form_renderer_id, form_data.username_element_renderer_id,
false));
}
TEST_F(AccountSelectFillDataTest, RetrieveSuggestionsOneForm) {
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
for (bool is_password_field : {false, true}) {
const FieldRendererId field_id =
is_password_field ? form_data_[0].password_element_renderer_id
: form_data_[0].username_element_renderer_id;
std::vector<UsernameAndRealm> suggestions =
account_select_fill_data.RetrieveSuggestions(
form_data_[0].form_renderer_id, field_id, is_password_field);
EXPECT_EQ(2u, suggestions.size());
EXPECT_EQ(base::ASCIIToUTF16(kUsernames[0]), suggestions[0].username);
EXPECT_EQ(std::string(), suggestions[0].realm);
EXPECT_EQ(base::ASCIIToUTF16(kAdditionalUsernames[0]),
suggestions[1].username);
EXPECT_EQ(std::string(), suggestions[1].realm);
}
}
TEST_F(AccountSelectFillDataTest, RetrieveSuggestionsTwoForm) {
// Test that after adding two PasswordFormFillData, suggestions for both forms
// are shown, with credentials from the second PasswordFormFillData. That
// emulates the case when credentials in the Password Store were changed
// between load the first and the second forms.
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
account_select_fill_data.Add(form_data_[1],
/*always_populate_realm=*/false);
std::vector<UsernameAndRealm> suggestions =
account_select_fill_data.RetrieveSuggestions(
form_data_[0].form_renderer_id,
form_data_[0].username_element_renderer_id, false);
EXPECT_EQ(1u, suggestions.size());
EXPECT_EQ(base::ASCIIToUTF16(kUsernames[1]), suggestions[0].username);
suggestions = account_select_fill_data.RetrieveSuggestions(
form_data_[1].form_renderer_id,
form_data_[1].username_element_renderer_id, false);
EXPECT_EQ(1u, suggestions.size());
EXPECT_EQ(base::ASCIIToUTF16(kUsernames[1]), suggestions[0].username);
}
// Test that, when sign-in uff is disabled, RetrieveSuggestions() returns all
// suggestions on password forms when there are only empty usernames, as the
// suggestions with empty usernames still hold passwords that can be filled.
// This test makes sure that there is no regression in password filling with
// sign-in uff disabled.
TEST_F(AccountSelectFillDataTest, RetrieveSuggestions_EmptyUsernames) {
PasswordFormFillData form_data = FormDataWithEmptyUsernamesOnly();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_THAT(account_select_fill_data.RetrieveSuggestions(
form_data.form_renderer_id,
form_data.username_element_renderer_id, false),
testing::SizeIs(2));
}
// Test that, when sign-in uff is enabled, RetrieveSuggestions() still returns
// all suggestions on password forms when there are only empty usernames, as the
// suggestions with empty usernames still hold passwords that can be filled.
// This test makes sure that there is no regression in password filling with
// sign-in uff disabled. Sign-in uff should only target single username forms.
TEST_F(AccountSelectFillDataTest,
RetrieveSuggestions_EmptyUsernames_WhenSigninUffEnabled) {
PasswordFormFillData form_data = FormDataWithEmptyUsernamesOnly();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_THAT(account_select_fill_data.RetrieveSuggestions(
form_data.form_renderer_id,
form_data.username_element_renderer_id, false),
testing::SizeIs(2));
}
TEST_F(AccountSelectFillDataTest,
RetrieveSuggestions_OnSingleUsernameForm_WhenEligible) {
PasswordFormFillData form_data = EligibleSingleUsernameFormData();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_THAT(account_select_fill_data.RetrieveSuggestions(
form_data.form_renderer_id,
form_data.username_element_renderer_id, false),
testing::SizeIs(2));
}
TEST_F(AccountSelectFillDataTest,
RetrieveSuggestions_OnSingleUsernameForm_WhenIneligible) {
PasswordFormFillData form_data = IneligibleSingleUsernameFormData();
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
EXPECT_THAT(account_select_fill_data.RetrieveSuggestions(
form_data.form_renderer_id,
form_data.username_element_renderer_id, false),
testing::IsEmpty());
}
TEST_F(AccountSelectFillDataTest, RetrievePSLMatchedSuggestions) {
AccountSelectFillData account_select_fill_data;
const char* kRealm = "http://a.example.com/";
std::string kAdditionalRealm = "http://b.example.com/";
// Make logins to be PSL matched by setting non-empy realm.
form_data_[0].preferred_login.realm = kRealm;
form_data_[0].additional_logins.begin()->realm = kAdditionalRealm;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
std::vector<UsernameAndRealm> suggestions =
account_select_fill_data.RetrieveSuggestions(
form_data_[0].form_renderer_id,
form_data_[0].username_element_renderer_id, false);
EXPECT_EQ(2u, suggestions.size());
EXPECT_EQ(base::ASCIIToUTF16(kUsernames[0]), suggestions[0].username);
EXPECT_EQ(kRealm, suggestions[0].realm);
EXPECT_EQ(base::ASCIIToUTF16(kAdditionalUsernames[0]),
suggestions[1].username);
EXPECT_EQ(kAdditionalRealm, suggestions[1].realm);
}
TEST_F(AccountSelectFillDataTest, GetFillData) {
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
account_select_fill_data.Add(form_data_[1],
/*always_populate_realm=*/false);
for (bool is_password_field : {false, true}) {
for (size_t form_i = 0; form_i < std::size(form_data_); ++form_i) {
const auto& form_data = form_data_[form_i];
// Suggestions should be shown on any password field on the form. So in
// case of clicking on a password field it is taken an id different from
// existing field ids.
const FieldRendererId password_field_id =
is_password_field ? FieldRendererId(1000)
: form_data.password_element_renderer_id;
const FieldRendererId clicked_field =
is_password_field ? password_field_id
: form_data.username_element_renderer_id;
// GetFillData() doesn't have form identifier in arguments, it should be
// provided in RetrieveSuggestions().
account_select_fill_data.RetrieveSuggestions(
form_data.form_renderer_id, clicked_field, is_password_field);
std::unique_ptr<FillData> fill_data =
account_select_fill_data.GetFillData(
base::ASCIIToUTF16(kUsernames[1]));
ASSERT_TRUE(fill_data);
EXPECT_EQ(form_data.url, fill_data->origin);
EXPECT_EQ(form_data.form_renderer_id.value(), fill_data->form_id.value());
EXPECT_EQ(kUsernameUniqueIDs[form_i],
fill_data->username_element_id.value());
EXPECT_EQ(base::ASCIIToUTF16(kUsernames[1]), fill_data->username_value);
EXPECT_EQ(password_field_id, fill_data->password_element_id);
EXPECT_EQ(base::ASCIIToUTF16(kPasswords[1]), fill_data->password_value);
}
}
}
TEST_F(AccountSelectFillDataTest, GetFillDataOldCredentials) {
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/false);
account_select_fill_data.Add(form_data_[1],
/*always_populate_realm=*/false);
// GetFillData() doesn't have form identifier in arguments, it should be
// provided in RetrieveSuggestions().
account_select_fill_data.RetrieveSuggestions(
form_data_[0].form_renderer_id,
form_data_[0].username_element_renderer_id, false);
// AccountSelectFillData should keep only last credentials. Check that in
// request of old credentials nothing is returned.
std::unique_ptr<FillData> fill_data =
account_select_fill_data.GetFillData(base::ASCIIToUTF16(kUsernames[0]));
EXPECT_FALSE(fill_data);
}
TEST_F(AccountSelectFillDataTest, CrossOriginSuggestionHasRealm) {
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data_[0],
/*always_populate_realm=*/true);
for (bool is_password_field : {false, true}) {
const FieldRendererId field_id =
is_password_field ? form_data_[0].password_element_renderer_id
: form_data_[0].username_element_renderer_id;
std::vector<UsernameAndRealm> suggestions =
account_select_fill_data.RetrieveSuggestions(
form_data_[0].form_renderer_id, field_id, is_password_field);
EXPECT_EQ(2u, suggestions.size());
EXPECT_EQ(kUrl, suggestions[0].realm);
EXPECT_EQ(kUrl, suggestions[1].realm);
}
}
// Tests getting existing form info for an existing username field.
TEST_F(AccountSelectFillDataTest, GetFormInfo_FocusedOnExistingUsernameField) {
const auto& form_data = form_data_[0];
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
const password_manager::FormInfo* form_info =
account_select_fill_data.GetFormInfo(
form_data.form_renderer_id, form_data.username_element_renderer_id,
/*is_password_field=*/false);
ASSERT_TRUE(form_info);
EXPECT_EQ(form_data.url, form_info->origin);
EXPECT_EQ(form_data.form_renderer_id, form_info->form_id);
EXPECT_EQ(form_data.username_element_renderer_id,
form_info->username_element_id);
EXPECT_EQ(form_data.password_element_renderer_id,
form_info->password_element_id);
}
// Tests getting form info for an unexisting username field that was no added to
// the data.
TEST_F(AccountSelectFillDataTest,
GetFormInfo_FocusedOnUnexistingUsernameField) {
const auto& form_data = form_data_[0];
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
const password_manager::FormInfo* form_info =
account_select_fill_data.GetFormInfo(form_data.form_renderer_id,
UnexistingFieldRendererId(),
/*is_password_field=*/false);
EXPECT_FALSE(form_info);
}
// Tests getting existing form info when focus on a random password field.
TEST_F(AccountSelectFillDataTest, GetFormInfo_FocusedOnPasswordField) {
const auto& form_data = form_data_[0];
AccountSelectFillData account_select_fill_data;
account_select_fill_data.Add(form_data, /*always_populate_realm=*/false);
// Get form info for a password field with a unexisting field renderer ID,
// which should still give a non-null result because any password field should
// get form info.
const password_manager::FormInfo* form_info =
account_select_fill_data.GetFormInfo(form_data.form_renderer_id,
UnexistingFieldRendererId(),
/*is_password_field=*/true);
ASSERT_TRUE(form_info);
EXPECT_EQ(form_data.url, form_info->origin);
EXPECT_EQ(form_data.form_renderer_id, form_info->form_id);
EXPECT_EQ(form_data.username_element_renderer_id,
form_info->username_element_id);
EXPECT_EQ(form_data.password_element_renderer_id,
form_info->password_element_id);
}
// Test getting form info when there is no data for the form.
TEST_F(AccountSelectFillDataTest, GetFormInfo_NoMatch) {
const auto& form_data = form_data_[0];
AccountSelectFillData account_select_fill_data;
const password_manager::FormInfo* form_info =
account_select_fill_data.GetFormInfo(
form_data.form_renderer_id, form_data.username_element_renderer_id,
/*is_password_field=*/false);
EXPECT_FALSE(form_info);
}
} // namespace