// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_test_utils.h"
#include "components/password_manager/core/browser/password_store/fake_password_store_backend.h"
#include "components/password_manager/core/browser/password_store/mock_password_store_backend.h"
#include "components/password_manager/core/browser/password_store/password_store_backend.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_registry.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/sync/base/pref_names.h"
#include "components/sync/service/sync_prefs.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores;
using password_manager::prefs::UseUpmLocalAndSeparateStoresState;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::UnorderedElementsAreArray;
using ::testing::VariantWith;
using ::testing::WithArg;
namespace password_manager {
namespace {
constexpr base::TimeDelta kLatencyDelta = base::Milliseconds(123u);
const PasswordStoreBackendError kBackendError =
PasswordStoreBackendError(PasswordStoreBackendErrorType::kUncategorized);
const char kMigrationProgressStateHistogram[] =
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"ProgressState";
const char kMigrationLatencyHistogram[] =
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers.Latency";
const char kMigrationSuccessHistogram[] =
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers.Success";
PasswordForm CreateTestPasswordForm(int index = 0) {
PasswordForm form;
form.url = GURL("https://test" + base::NumberToString(index) + ".com");
form.signon_realm = form.url.spec();
form.username_value = u"username" + base::NumberToString16(index);
form.password_value = u"password" + base::NumberToString16(index);
form.in_store = PasswordForm::Store::kProfileStore;
return form;
}
} // namespace
// Checks that the migration is started only when all the conditions are
// satisfied. It also check that migration result is properly recorded in prefs.
class BuiltInBackendToAndroidBackendMigratorTest : public testing::Test {
protected:
BuiltInBackendToAndroidBackendMigratorTest() = default;
~BuiltInBackendToAndroidBackendMigratorTest() override = default;
void Init(int current_migration_version = 0) {
prefs_.registry()->RegisterIntegerPref(
prefs::kCurrentMigrationVersionToGoogleMobileServices, 0);
prefs_.SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices,
current_migration_version);
prefs_.registry()->RegisterDoublePref(prefs::kTimeOfLastMigrationAttempt,
0.0);
prefs_.registry()->RegisterStringPref(
::prefs::kGoogleServicesLastSyncingUsername, "[email protected]");
prefs_.registry()->RegisterBooleanPref(
prefs::kUnenrolledFromGoogleMobileServicesDueToErrors, false);
prefs_.registry()->RegisterIntegerPref(
prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOff));
prefs_.registry()->RegisterBooleanPref(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup, false);
prefs_.registry()->RegisterBooleanPref(
prefs::kEmptyProfileStoreLoginDatabase, false);
prefs_.registry()->RegisterBooleanPref(
syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete, false);
prefs_.registry()->RegisterBooleanPref(
syncer::prefs::internal::kSyncKeepEverythingSynced, false);
prefs_.registry()->RegisterBooleanPref(
base::StrCat({syncer::prefs::internal::
kSyncDataTypeStatusForSyncToSigninMigrationPrefix,
".",
syncer::GetDataTypeLowerCaseRootTag(syncer::PASSWORDS)}),
false);
CreateMigrator(&built_in_backend_, &android_backend_, &prefs_);
}
void InitSyncService(bool is_password_sync_enabled) {
if (is_password_sync_enabled) {
sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false,
/*types=*/{syncer::UserSelectableType::kPasswords});
} else {
sync_service_.GetUserSettings()->SetSelectedTypes(
/*sync_everything=*/false, /*types=*/{});
}
migrator()->OnSyncServiceInitialized(&sync_service_);
}
// BuiltInBackendToAndroidBackendMigrator reads whether password sync is
// enabled from a pref rather than the SyncService. This helper sets such
// pref.
void SetPasswordSyncEnabledPref(bool enabled) {
if (enabled) {
prefs_.SetBoolean(
syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete, true);
prefs_.SetBoolean(syncer::prefs::internal::kSyncKeepEverythingSynced,
true);
prefs_.SetBoolean(
base::StrCat(
{syncer::prefs::internal::
kSyncDataTypeStatusForSyncToSigninMigrationPrefix,
".", syncer::GetDataTypeLowerCaseRootTag(syncer::PASSWORDS)}),
true);
} else {
prefs_.SetBoolean(
syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete, false);
}
}
void CreateMigrator(PasswordStoreBackend* built_in_backend,
PasswordStoreBackend* android_backend,
PrefService* prefs) {
migrator_ = std::make_unique<BuiltInBackendToAndroidBackendMigrator>(
built_in_backend, android_backend, prefs);
}
PasswordStoreBackend& built_in_backend() { return built_in_backend_; }
PasswordStoreBackend& android_backend() { return android_backend_; }
TestingPrefServiceSimple* prefs() { return &prefs_; }
BuiltInBackendToAndroidBackendMigrator* migrator() { return migrator_.get(); }
void RunUntilIdle() { task_env_.RunUntilIdle(); }
void FastForwardBy(base::TimeDelta delta) { task_env_.FastForwardBy(delta); }
private:
base::test::SingleThreadTaskEnvironment task_env_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
TestingPrefServiceSimple prefs_;
syncer::TestSyncService sync_service_;
FakePasswordStoreBackend built_in_backend_;
FakePasswordStoreBackend android_backend_{
IsAccountStore(false),
FakePasswordStoreBackend::UpdateAlwaysSucceeds(true)};
std::unique_ptr<BuiltInBackendToAndroidBackendMigrator> migrator_;
};
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
CurrentMigrationVersionIsUpdatedWhenMigrationIsNeeded) {
Init();
InitSyncService(/*is_password_sync_enabled=*/false);
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(1, prefs()->GetInteger(
prefs::kCurrentMigrationVersionToGoogleMobileServices));
EXPECT_EQ(
base::Time::Now().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
LocalPasswordMigrationRecordsProgressState) {
base::HistogramTester histogram_tester;
Init();
prefs()->SetInteger(
password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::
kOffAndMigrationPending));
InitSyncService(/*is_password_sync_enabled=*/false);
migrator()->StartMigrationOfLocalPasswords();
histogram_tester.ExpectBucketCount(
kMigrationProgressStateHistogram,
metrics_util::LocalPwdMigrationProgressState::kStarted, 1);
RunUntilIdle();
ASSERT_EQ(static_cast<int>(UseUpmLocalAndSeparateStoresState::kOn),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
histogram_tester.ExpectBucketCount(
kMigrationProgressStateHistogram,
metrics_util::LocalPwdMigrationProgressState::kFinished, 1);
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
AllPrefsAreUpdatedAfterLocalPasswordsMigration) {
Init();
prefs()->SetInteger(
password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::
kOffAndMigrationPending));
InitSyncService(/*is_password_sync_enabled=*/false);
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(static_cast<int>(UseUpmLocalAndSeparateStoresState::kOn),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
EXPECT_EQ(
base::Time::Now().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
EXPECT_TRUE(prefs()->GetBoolean(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
AllPrefsAreUpdatedAfterOnlySettingsMigration) {
Init();
prefs()->SetInteger(
password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::
kOffAndMigrationPending));
prefs()->SetBoolean(password_manager::prefs::kEmptyProfileStoreLoginDatabase,
true);
InitSyncService(/*is_password_sync_enabled=*/false);
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(static_cast<int>(UseUpmLocalAndSeparateStoresState::kOn),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
EXPECT_EQ(
base::Time::Now().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
EXPECT_FALSE(prefs()->GetBoolean(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
}
// The user was syncing and got unenrolled, but then disabled sync and the
// unenrollment pref wasn't reset.
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
PostMigrationSheetIsScheduledForLocalUnenrolledUsers) {
Init();
SetPasswordSyncEnabledPref(false);
prefs()->SetBoolean(prefs::kUnenrolledFromGoogleMobileServicesDueToErrors,
true);
InitSyncService(/*is_password_sync_enabled=*/false);
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(static_cast<int>(UseUpmLocalAndSeparateStoresState::kOn),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
EXPECT_EQ(
base::Time::Now().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
EXPECT_TRUE(prefs()->GetBoolean(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
PostMigrationSheetIsNotScheduledForUnenrolledUsers) {
Init();
SetPasswordSyncEnabledPref(true);
prefs()->SetBoolean(prefs::kUnenrolledFromGoogleMobileServicesDueToErrors,
true);
InitSyncService(/*is_password_sync_enabled=*/true);
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(static_cast<int>(UseUpmLocalAndSeparateStoresState::kOn),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
EXPECT_EQ(
base::Time::Now().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
EXPECT_FALSE(prefs()->GetBoolean(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
PostMigrationSheetIsNotScheduledForNotMigratedUsers) {
Init();
SetPasswordSyncEnabledPref(true);
prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices, 0);
InitSyncService(/*is_password_sync_enabled=*/true);
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(static_cast<int>(UseUpmLocalAndSeparateStoresState::kOn),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
EXPECT_EQ(
base::Time::Now().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
EXPECT_FALSE(prefs()->GetBoolean(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
AttemptPrefIsUpdatedAfterLocalMigrationStarted) {
Init();
ASSERT_EQ(
base::Time().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(
base::Time::Now().InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
PrefsUnchangedWhenAttemptedMigrationEarlierToday) {
Init();
prefs()->SetDouble(
password_manager::prefs::kTimeOfLastMigrationAttempt,
(base::Time::Now() - base::Hours(2)).InSecondsFSinceUnixEpoch());
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(0, prefs()->GetInteger(
prefs::kCurrentMigrationVersionToGoogleMobileServices));
EXPECT_EQ(
(base::Time::Now() - base::Hours(2)).InSecondsFSinceUnixEpoch(),
prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
MigrationNeverStartedMetrics) {
base::HistogramTester histogram_tester;
const char kMigrationFinishedMetric[] =
"PasswordManager.UnifiedPasswordManager.WasMigrationDone";
Init();
histogram_tester.ExpectTotalCount(kMigrationFinishedMetric, 1);
histogram_tester.ExpectBucketCount(kMigrationFinishedMetric, false, 1);
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTest, MigrationFinishedMetrics) {
base::HistogramTester histogram_tester;
const char kMigrationFinishedMetric[] =
"PasswordManager.UnifiedPasswordManager.WasMigrationDone";
Init(/*current_migration_version=*/1);
histogram_tester.ExpectUniqueSample(kMigrationFinishedMetric, true, 1);
}
// Tests that migration removes blocklisted entries with non-empty username or
// values from the built in backlend before writing to the Android backend.
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
MigrationClearsBlocklistedCredentials) {
Init();
InitSyncService(/*is_password_sync_enabled=*/false);
// Add two incorrect entries to the local database to check if they will be
// removed before writing to the android backend
PasswordForm form_1 = CreateTestPasswordForm(1);
form_1.blocked_by_user = true;
form_1.username_value.clear();
built_in_backend().AddLoginAsync(form_1, base::DoNothing());
PasswordForm form_2 = CreateTestPasswordForm(2);
form_2.blocked_by_user = true;
form_1.password_value.clear();
built_in_backend().AddLoginAsync(form_2, base::DoNothing());
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
base::MockCallback<LoginsOrErrorReply> mock_reply;
// Credentials should be cleaned in both android and built in backends.
EXPECT_CALL(mock_reply, Run(VariantWith<LoginsResult>((IsEmpty())))).Times(2);
android_backend().GetAllLoginsAsync(mock_reply.Get());
built_in_backend().GetAllLoginsAsync(mock_reply.Get());
RunUntilIdle();
EXPECT_TRUE(prefs()->GetBoolean(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
}
// Tests that migration does not affect username and password for
// non-blocklisted entries.
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
MigrationDoesNotClearNonBlocklistedCredentials) {
Init();
InitSyncService(/*is_password_sync_enabled=*/false);
// Add two incorrect entries to the local database to check if they will be
// fixed before writing to the android backend
PasswordForm form_1 = CreateTestPasswordForm(1);
built_in_backend().AddLoginAsync(form_1, base::DoNothing());
PasswordForm form_2 = CreateTestPasswordForm(2);
built_in_backend().AddLoginAsync(form_2, base::DoNothing());
// Add one form to be updated.
android_backend().AddLoginAsync(form_1, base::DoNothing());
RunUntilIdle();
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
base::MockCallback<LoginsOrErrorReply> mock_reply;
// Credentials should be cleaned in both android and built in backends.
EXPECT_CALL(mock_reply,
Run(VariantWith<LoginsResult>(ElementsAre(form_1, form_2))))
.Times(2);
android_backend().GetAllLoginsAsync(mock_reply.Get());
built_in_backend().GetAllLoginsAsync(mock_reply.Get());
RunUntilIdle();
EXPECT_TRUE(prefs()->GetBoolean(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
}
// The user was syncing and got unenrolled, but then disabled sync and the
// unenrollment pref wasn't reset.
TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
UnenrollmentPrefsAreResetOnLocalPasswordMigration) {
Init();
prefs()->SetBoolean(prefs::kUnenrolledFromGoogleMobileServicesDueToErrors,
true);
prefs()->SetInteger(
kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
UseUpmLocalAndSeparateStoresState::kOffAndMigrationPending));
InitSyncService(/*is_password_sync_enabled=*/true);
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
EXPECT_EQ(static_cast<int>(UseUpmLocalAndSeparateStoresState::kOn),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
EXPECT_FALSE(prefs()->GetBoolean(
prefs::kUnenrolledFromGoogleMobileServicesDueToErrors));
}
class BuiltInBackendToAndroidBackendMigratorTestWithMockedBackends
: public BuiltInBackendToAndroidBackendMigratorTest {
protected:
BuiltInBackendToAndroidBackendMigratorTestWithMockedBackends() = default;
~BuiltInBackendToAndroidBackendMigratorTestWithMockedBackends() override =
default;
void Init(int current_migration_version = 0) {
BuiltInBackendToAndroidBackendMigratorTest::Init(current_migration_version);
CreateMigrator(&built_in_backend_, &android_backend_, prefs());
}
MockPasswordStoreBackend built_in_backend_;
MockPasswordStoreBackend android_backend_;
};
TEST_F(BuiltInBackendToAndroidBackendMigratorTestWithMockedBackends,
RemoveBlocklistedReturnsWithErrorDoesntCrash) {
Init();
InitSyncService(/*is_password_sync_enabled=*/false);
base::HistogramTester histogram_tester;
PasswordForm form = CreateTestPasswordForm(1);
form.blocked_by_user = true;
std::vector<PasswordForm> built_in_logins = {std::move(form)};
EXPECT_CALL(built_in_backend_, GetAllLoginsAsync)
.WillOnce(base::test::RunOnceCallback<0>(std::move(built_in_logins)));
// Set up `RemoveLoginAsync` to return an error.
EXPECT_CALL(built_in_backend_, RemoveLoginAsync)
.WillOnce(base::test::RunOnceCallback<2>(kBackendError));
migrator()->StartMigrationOfLocalPasswords();
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"BuiltInBackend.RemoveLogin.Success",
false, 1);
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTestWithMockedBackends,
UpdateLoginMetricReportsSuccess) {
Init();
InitSyncService(/*is_password_sync_enabled=*/false);
base::HistogramTester histogram_tester;
// Set up conflicting passwords in the 2 backends, with the built-in backend
// containing the most recently used.
PasswordForm older_form = CreateTestPasswordForm(0);
PasswordForm newer_form = older_form;
newer_form.date_last_used = newer_form.date_last_used + base::Days(1);
newer_form.password_value = u"different password";
EXPECT_CALL(built_in_backend_, GetAllLoginsAsync)
.WillOnce(base::test::RunOnceCallback<0>(std::vector{newer_form}));
EXPECT_CALL(android_backend_, GetAllLoginsAsync)
.WillOnce(base::test::RunOnceCallback<0>(std::vector{older_form}));
EXPECT_CALL(android_backend_, UpdateLoginAsync)
.WillOnce(base::test::RunOnceCallback<1>(PasswordChangesOrError()));
migrator()->StartMigrationOfLocalPasswords();
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"AndroidBackend."
"UpdateLogin.Success",
true, 1);
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTestWithMockedBackends,
UpdateLoginMetricReportsFailure) {
Init();
InitSyncService(/*is_password_sync_enabled=*/false);
base::HistogramTester histogram_tester;
// Set up conflicting passwords in the 2 backends, with the built-in backend
// containing the most recently used.
PasswordForm older_form = CreateTestPasswordForm(0);
PasswordForm newer_form = older_form;
newer_form.date_last_used = newer_form.date_last_used + base::Days(1);
newer_form.password_value = u"different password";
EXPECT_CALL(built_in_backend_, GetAllLoginsAsync)
.WillOnce(base::test::RunOnceCallback<0>(std::vector{newer_form}));
EXPECT_CALL(android_backend_, GetAllLoginsAsync)
.WillOnce(base::test::RunOnceCallback<0>(std::vector{older_form}));
EXPECT_CALL(android_backend_, UpdateLoginAsync)
.WillOnce(base::test::RunOnceCallback<1>(kBackendError));
migrator()->StartMigrationOfLocalPasswords();
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"AndroidBackend.UpdateLogin.Success",
false, 1);
}
TEST_F(BuiltInBackendToAndroidBackendMigratorTestWithMockedBackends,
RemoveLoginMetricReportsSuccess) {
Init();
InitSyncService(/*is_password_sync_enabled=*/false);
base::HistogramTester histogram_tester;
PasswordForm form_1 = CreateTestPasswordForm(1);
form_1.blocked_by_user = true;
std::vector<PasswordForm> built_in_logins = {std::move(form_1)};
EXPECT_CALL(built_in_backend_, GetAllLoginsAsync)
.WillOnce(base::test::RunOnceCallback<0>(std::move(built_in_logins)));
EXPECT_CALL(built_in_backend_, RemoveLoginAsync)
.WillOnce(base::test::RunOnceCallback<2>(PasswordChangesOrError()));
migrator()->StartMigrationOfLocalPasswords();
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"BuiltInBackend.RemoveLogin.Success",
true, 1);
}
// Holds the built in and android backend's logins and the expected result after
// the migration.
struct MigrationParam {
struct Entry {
Entry(int index,
std::string password = "",
base::TimeDelta date_created = base::TimeDelta(),
base::TimeDelta date_last_used = base::TimeDelta(),
base::TimeDelta date_password_modified = base::TimeDelta())
: index(index),
password(password),
date_created(date_created),
date_last_used(date_last_used),
date_password_modified(date_password_modified) {}
PasswordForm ToPasswordForm() const {
PasswordForm form = CreateTestPasswordForm(index);
form.password_value = base::ASCIIToUTF16(password);
form.date_created = base::Time() + date_created;
form.date_last_used = base::Time() + date_last_used;
form.date_password_modified = base::Time() + date_password_modified;
return form;
}
int index;
std::string password;
base::TimeDelta date_created;
base::TimeDelta date_last_used;
base::TimeDelta date_password_modified;
};
std::vector<PasswordForm> GetBuiltInLogins() const {
return EntriesToPasswordForms(built_in_logins);
}
std::vector<PasswordForm> GetAndroidLogins() const {
return EntriesToPasswordForms(android_logins);
}
std::vector<PasswordForm> GetMergedLogins() const {
return EntriesToPasswordForms(merged_logins);
}
std::vector<PasswordForm> EntriesToPasswordForms(
const std::vector<Entry>& entries) const {
std::vector<PasswordForm> v;
base::ranges::transform(entries, std::back_inserter(v),
&Entry::ToPasswordForm);
return v;
}
std::vector<Entry> built_in_logins;
std::vector<Entry> android_logins;
std::vector<Entry> merged_logins;
int updated_in_android_backend_credentials_count = 0;
// The local passwords migration uses a merge algorithm for credentials
// with the same primary key in both backends,but different passwords.
// The conflicts are resolved in favor or the most recently created/mdoified
// entry. The conflict is won by the android backend when the most
// recent credential is stored there.
int conflicts_won_by_android = 0;
};
// Tests that the migration actually works by comparing passwords in
// built-in/android backend before and after migration.
class BuiltInBackendToAndroidBackendMigratorTestWithMigrationParams
: public BuiltInBackendToAndroidBackendMigratorTest,
public testing::WithParamInterface<MigrationParam> {};
// Tests the migration result.
TEST_P(BuiltInBackendToAndroidBackendMigratorTestWithMigrationParams,
LocalPwdMigrationAfterEnrollingIntoTheExperiment) {
base::HistogramTester histogram_tester;
// Set current_migration_version to 0 to imitate a user enrolling into the
// experiment.
BuiltInBackendToAndroidBackendMigratorTest::Init(
/*current_migration_version=*/0);
InitSyncService(/*is_password_sync_enabled=*/false);
prefs()->SetInteger(
password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::
kOffAndMigrationPending));
const MigrationParam& p = GetParam();
for (const auto& login : p.GetBuiltInLogins()) {
built_in_backend().AddLoginAsync(login, base::DoNothing());
}
for (const auto& login : p.GetAndroidLogins()) {
android_backend().AddLoginAsync(login, base::DoNothing());
}
RunUntilIdle();
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
base::MockCallback<LoginsOrErrorReply> mock_reply;
EXPECT_CALL(
mock_reply,
Run(VariantWith<LoginsResult>(ElementsAreArray(p.GetMergedLogins()))));
android_backend().GetAllLoginsAsync(mock_reply.Get());
RunUntilIdle();
// After local passwords migration, the credentials in the built in backend
// should stay unchanged.
EXPECT_CALL(
mock_reply,
Run(VariantWith<LoginsResult>(ElementsAreArray(p.GetBuiltInLogins()))));
built_in_backend().GetAllLoginsAsync(mock_reply.Get());
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"MergeWhereAndroidHasMostRecent",
GetParam().conflicts_won_by_android, 1);
}
TEST_P(BuiltInBackendToAndroidBackendMigratorTestWithMigrationParams,
LocalPasswordsMigrationMetrics) {
base::HistogramTester histogram_tester;
BuiltInBackendToAndroidBackendMigratorTest::Init(
/*current_migration_version=*/0);
prefs()->SetInteger(
password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::
kOffAndMigrationPending));
const MigrationParam& p = GetParam();
for (const auto& login : p.GetBuiltInLogins()) {
built_in_backend().AddLoginAsync(login, base::DoNothing());
}
for (const auto& login : p.GetAndroidLogins()) {
android_backend().AddLoginAsync(login, base::DoNothing());
}
RunUntilIdle();
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
int added_to_android_backend_count =
p.GetMergedLogins().size() - p.GetAndroidLogins().size();
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"AddLoginCount",
added_to_android_backend_count, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"UpdateLoginCount",
p.updated_in_android_backend_credentials_count, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"MigratedLoginsTotalCount",
p.updated_in_android_backend_credentials_count +
added_to_android_backend_count,
1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"AndroidBackend."
"AddLogin.Success",
true, added_to_android_backend_count);
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"AndroidBackend."
"UpdateLogin.Success",
true, p.updated_in_android_backend_credentials_count);
}
INSTANTIATE_TEST_SUITE_P(
BuiltInBackendToAndroidBackendMigratorTest,
BuiltInBackendToAndroidBackendMigratorTestWithMigrationParams,
testing::Values(
MigrationParam{.built_in_logins = {},
.android_logins = {},
.merged_logins = {},
.conflicts_won_by_android = 0},
MigrationParam{.built_in_logins = {{1}, {2}},
.android_logins = {},
.merged_logins = {{1}, {2}},
.updated_in_android_backend_credentials_count = 0,
.conflicts_won_by_android = 0},
MigrationParam{.built_in_logins = {},
.android_logins = {{1}, {2}},
.merged_logins = {{1}, {2}},
.conflicts_won_by_android = 0},
MigrationParam{.built_in_logins = {{1}, {2}},
.android_logins = {{3}},
.merged_logins = {{1}, {2}, {3}},
.conflicts_won_by_android = 0},
MigrationParam{.built_in_logins = {{1}, {2}, {3}},
.android_logins = {{1}, {2}, {3}},
.merged_logins = {{1}, {2}, {3}},
.conflicts_won_by_android = 0},
MigrationParam{
.built_in_logins = {{1, "old_password", base::Days(1)}, {2}},
.android_logins = {{1, "new_password", base::Days(2)}, {3}},
.merged_logins = {{1, "new_password", base::Days(2)}, {2}, {3}},
.conflicts_won_by_android = 1},
MigrationParam{
.built_in_logins = {{1, "new_password", base::Days(2)}, {2}},
.android_logins = {{1, "old_password", base::Days(1)}, {3}},
.merged_logins = {{1, "new_password", base::Days(2)}, {2}, {3}},
.updated_in_android_backend_credentials_count = 1,
.conflicts_won_by_android = 0},
MigrationParam{.built_in_logins = {{1, "new_password",
/*date_created=*/base::Days(1),
/*date_last_used=*/base::Days(2)}},
.android_logins = {{1, "old_password",
/*date_created=*/base::Days(1)}},
.merged_logins = {{1, "new_password", base::Days(1),
/*date_last_used=*/base::Days(2)}},
.updated_in_android_backend_credentials_count = 1,
.conflicts_won_by_android = 0},
MigrationParam{
.built_in_logins = {{1, "old_password",
/*date_created=*/base::Days(1),
/*date_last_used=*/base::Days(2),
/*date_password_modified=*/base::Days(2)}},
.android_logins = {{1, "new_password",
/*date_created=*/base::Days(1),
/*date_last_used=*/base::Days(2),
/*date_password_modified=*/base::Days(3)}},
.merged_logins = {{1, "new_password",
/*date_created=*/base::Days(1),
/*date_last_used=*/base::Days(2),
/*date_password_modified=*/base::Days(3)}},
.conflicts_won_by_android = 1}));
struct MigrationParamForMetrics {
// Whether migration has already happened.
bool migration_ran_before;
// Whether password sync is enabled in settings.
bool is_sync_enabled;
// Whether migration should complete successfully or not.
bool is_successful_migration;
};
class BuiltInBackendToAndroidBackendMigratorTestMetrics
: public BuiltInBackendToAndroidBackendMigratorTest,
public testing::WithParamInterface<MigrationParamForMetrics> {
protected:
BuiltInBackendToAndroidBackendMigratorTestMetrics() {
prefs()->registry()->RegisterIntegerPref(
prefs::kCurrentMigrationVersionToGoogleMobileServices, 0);
prefs()->registry()->RegisterDoublePref(prefs::kTimeOfLastMigrationAttempt,
0.0);
prefs()->registry()->RegisterStringPref(
::prefs::kGoogleServicesLastSyncingUsername, "[email protected]");
prefs()->registry()->RegisterBooleanPref(
prefs::kUnenrolledFromGoogleMobileServicesDueToErrors, false);
prefs()->registry()->RegisterIntegerPref(
prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOff));
prefs()->registry()->RegisterBooleanPref(
prefs::kEmptyProfileStoreLoginDatabase, false);
prefs()->registry()->RegisterBooleanPref(
prefs::kShouldShowPostPasswordMigrationSheetAtStartup, false);
prefs()->registry()->RegisterBooleanPref(
syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete, false);
prefs()->registry()->RegisterBooleanPref(
syncer::prefs::internal::kSyncKeepEverythingSynced, false);
if (GetParam().migration_ran_before) {
// Setup the pref to indicate that the migration has happened already.
prefs()->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices,
1);
}
CreateMigrator(&built_in_backend_, &android_backend_, prefs());
}
::testing::StrictMock<MockPasswordStoreBackend> built_in_backend_;
::testing::StrictMock<MockPasswordStoreBackend> android_backend_;
};
TEST_P(BuiltInBackendToAndroidBackendMigratorTestMetrics,
MigrationMetricsTest) {
base::HistogramTester histogram_tester;
InitSyncService(/*is_password_sync_enabled=*/GetParam().is_sync_enabled);
auto test_migration_callback = [](LoginsOrErrorReply reply) -> void {
LoginsResultOrError result = GetParam().is_successful_migration
? LoginsResultOrError(LoginsResult())
: LoginsResultOrError(kBackendError);
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce(std::move(reply), std::move(result)),
kLatencyDelta);
};
EXPECT_CALL(built_in_backend_, GetAllLoginsAsync)
.WillOnce(WithArg<0>(Invoke(test_migration_callback)));
EXPECT_CALL(android_backend_, GetAllLoginsAsync)
.WillOnce(base::test::RunOnceCallback<0>(LoginsResult()));
migrator()->StartMigrationOfLocalPasswords();
FastForwardBy(kLatencyDelta);
histogram_tester.ExpectTotalCount(kMigrationLatencyHistogram, 1);
histogram_tester.ExpectTimeBucketCount(kMigrationLatencyHistogram,
kLatencyDelta, 1);
histogram_tester.ExpectUniqueSample(kMigrationSuccessHistogram,
GetParam().is_successful_migration, 1);
}
INSTANTIATE_TEST_SUITE_P(
BuiltInBackendToAndroidBackendMigratorTest,
BuiltInBackendToAndroidBackendMigratorTestMetrics,
testing::Values(
// Successful migration.
MigrationParamForMetrics{.migration_ran_before = false,
.is_sync_enabled = false,
.is_successful_migration = true},
// Unsuccessful migration.
MigrationParamForMetrics{.migration_ran_before = false,
.is_sync_enabled = false,
.is_successful_migration = false},
// Sync user, migration should not run.
MigrationParamForMetrics{.migration_ran_before = false,
.is_sync_enabled = true,
.is_successful_migration = false},
// User who already went through the migration. It should not run again.
MigrationParamForMetrics{.migration_ran_before = true,
.is_sync_enabled = false,
.is_successful_migration = false}));
class BuiltInBackendToAndroidBackendMigratorWithMockAndroidBackendTest
: public BuiltInBackendToAndroidBackendMigratorTest {
protected:
BuiltInBackendToAndroidBackendMigratorWithMockAndroidBackendTest() {
prefs()->registry()->RegisterIntegerPref(
prefs::kCurrentMigrationVersionToGoogleMobileServices, 0);
prefs()->registry()->RegisterDoublePref(prefs::kTimeOfLastMigrationAttempt,
0.0);
prefs()->registry()->RegisterStringPref(
::prefs::kGoogleServicesLastSyncingUsername, "[email protected]");
prefs()->registry()->RegisterIntegerPref(
prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOff));
CreateMigrator(&built_in_backend_, &android_backend_, prefs());
}
PasswordStoreBackend& built_in_backend() { return built_in_backend_; }
::testing::NiceMock<MockPasswordStoreBackend> android_backend_;
private:
FakePasswordStoreBackend built_in_backend_;
};
TEST_F(BuiltInBackendToAndroidBackendMigratorWithMockAndroidBackendTest,
DoesNotCompleteMigrationWhenWritingToAndroidBackendFails) {
prefs()->SetInteger(
password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::
kOffAndMigrationPending));
// Sync state doesn't affect this test, run it arbitrarily for non-sync'ing
// users.
InitSyncService(/*is_password_sync_enabled=*/false);
// Add two credentials to the built-in backend.
built_in_backend().AddLoginAsync(CreateTestPasswordForm(/*index=*/1),
base::DoNothing());
built_in_backend().AddLoginAsync(CreateTestPasswordForm(/*index=*/2),
base::DoNothing());
// Simulate an empty Android backend.
EXPECT_CALL(android_backend_, GetAllLoginsAsync)
.WillOnce(WithArg<0>(Invoke([](LoginsOrErrorReply reply) -> void {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(reply), LoginsResult()));
})));
// Simulate an Android backend that fails to write.
ON_CALL(android_backend_, AddLoginAsync)
.WillByDefault(
WithArg<1>(Invoke([](PasswordChangesOrErrorReply callback) -> void {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kBackendError));
})));
// Once one AddLoginAsync() call fails, all consecutive ones will not be
// executed. Check that exactly one AddLoginAsync() is called.
EXPECT_CALL(android_backend_, AddLoginAsync).Times(1);
migrator()->StartMigrationOfLocalPasswords();
// Local migration should still be pending since it didn' t complete
// successfully.
EXPECT_EQ(static_cast<int>(
UseUpmLocalAndSeparateStoresState::kOffAndMigrationPending),
prefs()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores));
RunUntilIdle();
}
TEST_F(BuiltInBackendToAndroidBackendMigratorWithMockAndroidBackendTest,
LocalPasswordsMigrationMetricsWithErrorDuringMigration) {
// Sets up the backends mocks in the way:
// - built in backend has 2 passwords;
// - android backend has no passwords;
// - android backend will return an error when trying to add a second built in
// password;
std::vector<PasswordForm> built_in_passwords = {CreateTestPasswordForm(0),
CreateTestPasswordForm(1)};
for (PasswordForm& form : built_in_passwords) {
built_in_backend().AddLoginAsync(form, base::DoNothing());
}
RunUntilIdle();
ON_CALL(android_backend_, GetAllLoginsAsync)
.WillByDefault(WithArg<0>(Invoke([&](LoginsOrErrorReply reply) -> void {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(reply), std::vector<PasswordForm>()));
})));
ON_CALL(android_backend_, AddLoginAsync(built_in_passwords[1], testing::_))
.WillByDefault(
WithArg<1>(Invoke([&](PasswordChangesOrErrorReply reply) -> void {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(reply), PasswordChangesOrError()));
})));
ON_CALL(android_backend_, AddLoginAsync(built_in_passwords[0], testing::_))
.WillByDefault(
WithArg<1>(Invoke([&](PasswordChangesOrErrorReply reply) -> void {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(reply), kBackendError));
})));
prefs()->SetInteger(
password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
static_cast<int>(
password_manager::prefs::UseUpmLocalAndSeparateStoresState::
kOffAndMigrationPending));
base::HistogramTester histogram_tester;
migrator()->StartMigrationOfLocalPasswords();
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"AddLoginCount",
1, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"UpdateLoginCount",
0, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.UnifiedPasswordManager.MigrationForLocalUsers."
"MigratedLoginsTotalCount",
1, 1);
}
} // namespace password_manager