// 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/password_store_proxy_backend.h"
#include <functional>
#include <memory>
#include <utility>
#include <vector>
#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/not_fatal_until.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "chrome/browser/password_manager/android/password_manager_android_util.h"
#include "components/password_manager/core/browser/features/password_features.h"
#include "components/password_manager/core/browser/password_store/password_store_backend_error.h"
#include "components/password_manager/core/browser/password_sync_util.h"
#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/sync/model/proxy_data_type_controller_delegate.h"
#include "components/sync/service/sync_service.h"
#include "google_apis/gaia/google_service_auth_error.h"
namespace password_manager {
namespace {
void InvokeCallbackWithCombinedStatus(base::OnceCallback<void(bool)> completion,
std::vector<bool> statuses) {
std::move(completion).Run(base::ranges::all_of(statuses, std::identity()));
}
void RecordPasswordDeletionResult(PasswordChangesOrError result) {
bool is_operation_successful = true;
if (absl::holds_alternative<PasswordStoreBackendError>(result)) {
is_operation_successful = false;
}
base::UmaHistogramBoolean(
"PasswordManager.PasswordStoreProxyBackend.PasswordRemovalStatus",
is_operation_successful);
if (!is_operation_successful) {
return;
}
PasswordChanges changes = absl::get<PasswordChanges>(std::move(result));
if (changes.has_value()) {
base::UmaHistogramCounts1000(
"PasswordManager.PasswordStoreProxyBackend.RemovedPasswordCount",
changes.value().size());
}
}
} // namespace
PasswordStoreProxyBackend::PasswordStoreProxyBackend(
std::unique_ptr<PasswordStoreBackend> built_in_backend,
std::unique_ptr<PasswordStoreBackend> android_backend,
PrefService* prefs)
: built_in_backend_(std::move(built_in_backend)),
android_backend_(std::move(android_backend)),
prefs_(prefs) {
CHECK(!password_manager::UsesSplitStoresAndUPMForLocal(prefs_));
}
PasswordStoreProxyBackend::~PasswordStoreProxyBackend() = default;
void PasswordStoreProxyBackend::InitBackend(
AffiliatedMatchHelper* affiliated_match_helper,
RemoteChangesReceived remote_form_changes_received,
base::RepeatingClosure sync_enabled_or_disabled_cb,
base::OnceCallback<void(bool)> completion) {
base::RepeatingCallback<void(bool)> pending_initialization_calls =
base::BarrierCallback<bool>(
/*num_callbacks=*/2, base::BindOnce(&InvokeCallbackWithCombinedStatus,
std::move(completion)));
// Both backends need to be initialized, so using the helpers for main/shadow
// backend is unnecessary and won't work since the sync status may not be
// available yet.
built_in_backend_->InitBackend(
affiliated_match_helper,
base::BindRepeating(
&PasswordStoreProxyBackend::OnRemoteFormChangesReceived,
weak_ptr_factory_.GetWeakPtr(),
CallbackOriginatesFromAndroidBackend(false),
remote_form_changes_received),
std::move(sync_enabled_or_disabled_cb),
base::BindOnce(pending_initialization_calls));
android_backend_->InitBackend(
affiliated_match_helper,
base::BindRepeating(
&PasswordStoreProxyBackend::OnRemoteFormChangesReceived,
weak_ptr_factory_.GetWeakPtr(),
CallbackOriginatesFromAndroidBackend(true),
std::move(remote_form_changes_received)),
base::NullCallback(), base::BindOnce(pending_initialization_calls));
}
void PasswordStoreProxyBackend::Shutdown(base::OnceClosure shutdown_completed) {
weak_ptr_factory_.InvalidateWeakPtrs();
base::RepeatingClosure pending_shutdown_calls = base::BarrierClosure(
/*num_closures=*/2, std::move(shutdown_completed));
android_backend_->Shutdown(pending_shutdown_calls);
built_in_backend_->Shutdown(pending_shutdown_calls);
android_backend_.reset();
built_in_backend_.reset();
}
bool PasswordStoreProxyBackend::IsAbleToSavePasswords() {
// shadow_backend()->IsAbleToSavePasswords() doesn't matter because it's a
// fallback.
return main_backend()->IsAbleToSavePasswords();
}
void PasswordStoreProxyBackend::GetAllLoginsAsync(LoginsOrErrorReply callback) {
main_backend()->GetAllLoginsAsync(std::move(callback));
}
void PasswordStoreProxyBackend::GetAllLoginsWithAffiliationAndBrandingAsync(
LoginsOrErrorReply callback) {
main_backend()->GetAllLoginsWithAffiliationAndBrandingAsync(
std::move(callback));
}
void PasswordStoreProxyBackend::GetAutofillableLoginsAsync(
LoginsOrErrorReply callback) {
main_backend()->GetAutofillableLoginsAsync(std::move(callback));
}
void PasswordStoreProxyBackend::GetAllLoginsForAccountAsync(
std::string account,
LoginsOrErrorReply callback) {
NOTREACHED_IN_MIGRATION();
}
void PasswordStoreProxyBackend::FillMatchingLoginsAsync(
LoginsOrErrorReply callback,
bool include_psl,
const std::vector<PasswordFormDigest>& forms) {
main_backend()->FillMatchingLoginsAsync(std::move(callback), include_psl,
forms);
}
void PasswordStoreProxyBackend::GetGroupedMatchingLoginsAsync(
const PasswordFormDigest& form_digest,
LoginsOrErrorReply callback) {
main_backend()->GetGroupedMatchingLoginsAsync(form_digest,
std::move(callback));
}
void PasswordStoreProxyBackend::AddLoginAsync(
const PasswordForm& form,
PasswordChangesOrErrorReply callback) {
PasswordChangesOrErrorReply result_callback;
main_backend()->AddLoginAsync(form, std::move(callback));
}
void PasswordStoreProxyBackend::UpdateLoginAsync(
const PasswordForm& form,
PasswordChangesOrErrorReply callback) {
PasswordChangesOrErrorReply result_callback;
main_backend()->UpdateLoginAsync(form, std::move(callback));
}
void PasswordStoreProxyBackend::RemoveLoginAsync(
const base::Location& location,
const PasswordForm& form,
PasswordChangesOrErrorReply callback) {
main_backend()->RemoveLoginAsync(location, form, std::move(callback));
if (UsesAndroidBackendAsMainBackend()) {
shadow_backend()->RemoveLoginAsync(location, form, base::DoNothing());
}
}
void PasswordStoreProxyBackend::RemoveLoginsByURLAndTimeAsync(
const base::Location& location,
const base::RepeatingCallback<bool(const GURL&)>& url_filter,
base::Time delete_begin,
base::Time delete_end,
base::OnceCallback<void(bool)> sync_completion,
PasswordChangesOrErrorReply callback) {
// The `sync_completion` callback is only relevant for account passwords
// which don't exist on Android, so it is not passed in and can be ignored
// later.
CHECK(!sync_completion);
main_backend()->RemoveLoginsByURLAndTimeAsync(
location, url_filter, delete_begin, delete_end, base::NullCallback(),
std::move(callback));
if (UsesAndroidBackendAsMainBackend()) {
shadow_backend()->RemoveLoginsByURLAndTimeAsync(
location, url_filter, std::move(delete_begin), std::move(delete_end),
base::NullCallback(), base::DoNothing());
}
}
void PasswordStoreProxyBackend::RemoveLoginsCreatedBetweenAsync(
const base::Location& location,
base::Time delete_begin,
base::Time delete_end,
PasswordChangesOrErrorReply callback) {
main_backend()->RemoveLoginsCreatedBetweenAsync(
location, delete_begin, delete_end, std::move(callback));
if (UsesAndroidBackendAsMainBackend()) {
shadow_backend()->RemoveLoginsCreatedBetweenAsync(
location, std::move(delete_begin), std::move(delete_end),
base::DoNothing());
}
}
void PasswordStoreProxyBackend::DisableAutoSignInForOriginsAsync(
const base::RepeatingCallback<bool(const GURL&)>& origin_filter,
base::OnceClosure completion) {
// TODO(crbug.com/40208332): Implement error handling, when actual
// store changes will be received from the store.
main_backend()->DisableAutoSignInForOriginsAsync(origin_filter,
std::move(completion));
}
SmartBubbleStatsStore* PasswordStoreProxyBackend::GetSmartBubbleStatsStore() {
return main_backend()->GetSmartBubbleStatsStore();
}
std::unique_ptr<syncer::DataTypeControllerDelegate>
PasswordStoreProxyBackend::CreateSyncControllerDelegate() {
return built_in_backend_->CreateSyncControllerDelegate();
}
void PasswordStoreProxyBackend::OnSyncServiceInitialized(
syncer::SyncService* sync_service) {
sync_service_ = sync_service;
sync_service_->AddObserver(this);
android_backend_->OnSyncServiceInitialized(sync_service);
MaybeClearBuiltInBackend();
if (!password_manager::sync_util::HasChosenToSyncPasswords(sync_service_)) {
// Reset initial UPM migration if password sync is disabled.
prefs_->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices,
0);
}
}
void PasswordStoreProxyBackend::RecordAddLoginAsyncCalledFromTheStore() {
main_backend()->RecordAddLoginAsyncCalledFromTheStore();
}
void PasswordStoreProxyBackend::RecordUpdateLoginAsyncCalledFromTheStore() {
main_backend()->RecordUpdateLoginAsyncCalledFromTheStore();
}
base::WeakPtr<PasswordStoreBackend> PasswordStoreProxyBackend::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
PasswordStoreBackend* PasswordStoreProxyBackend::main_backend() {
return UsesAndroidBackendAsMainBackend() ? android_backend_.get()
: built_in_backend_.get();
}
PasswordStoreBackend* PasswordStoreProxyBackend::shadow_backend() {
return UsesAndroidBackendAsMainBackend() ? built_in_backend_.get()
: android_backend_.get();
}
void PasswordStoreProxyBackend::OnStateChanged(syncer::SyncService* sync) {
if (!password_manager::sync_util::HasChosenToSyncPasswords(sync_service_)) {
// Reset initial UPM migration if password sync is disabled.
prefs_->SetInteger(prefs::kCurrentMigrationVersionToGoogleMobileServices,
0);
}
}
void PasswordStoreProxyBackend::OnSyncShutdown(
syncer::SyncService* sync_service) {
sync_service->RemoveObserver(this);
sync_service_ = nullptr;
}
void PasswordStoreProxyBackend::OnRemoteFormChangesReceived(
CallbackOriginatesFromAndroidBackend originates_from_android,
RemoteChangesReceived remote_form_changes_received,
std::optional<PasswordStoreChangeList> changes) {
// `remote_form_changes_received` is used to inform observers about changes in
// the backend. This check guarantees observers are informed only about
// changes in the main backend.
if (originates_from_android.value() == UsesAndroidBackendAsMainBackend()) {
remote_form_changes_received.Run(std::move(changes));
}
}
bool PasswordStoreProxyBackend::UsesAndroidBackendAsMainBackend() {
CHECK(sync_service_);
if (!password_manager::sync_util::HasChosenToSyncPasswords(sync_service_)) {
return false;
}
// If there are no passwords in the `LoginDatabase` UPM can be enabled
// regardless of other factors since if there are no passwords no migration is
// required.
if (prefs_->GetBoolean(prefs::kEmptyProfileStoreLoginDatabase)) {
return true;
}
// There are passwords in the `LoginDatabase`. In order to ensure that those
// passwords are available in the `android_backend_` the user has to not be
// unrolled and has to have finished the initial migration.
if (prefs_->GetBoolean(
prefs::kUnenrolledFromGoogleMobileServicesDueToErrors) ||
prefs_->GetInteger(
prefs::kCurrentMigrationVersionToGoogleMobileServices) == 0) {
return false;
}
return true;
}
void PasswordStoreProxyBackend::MaybeClearBuiltInBackend() {
CHECK(!password_manager::UsesSplitStoresAndUPMForLocal(prefs_));
// Don't do anything if password syncing is not enabled.
if (!password_manager::sync_util::HasChosenToSyncPasswords(sync_service_)) {
return;
}
// Don't do anything if the user didn't complete initial UPM migration or was
// unenrolled in the past.
if (prefs_->GetInteger(
prefs::kCurrentMigrationVersionToGoogleMobileServices) == 0 ||
prefs_->GetBoolean(
prefs::kUnenrolledFromGoogleMobileServicesDueToErrors)) {
return;
}
built_in_backend_->RemoveLoginsCreatedBetweenAsync(
FROM_HERE, base::Time(), base::Time::Max(),
base::BindOnce(&RecordPasswordDeletionResult));
}
} // namespace password_manager