blob: a0c55cf1bf8379a0e7a4f7d7765a51cee0c24c7e [file] [log] [blame]
// 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.
#include "components/password_manager/core/browser/sync/password_model_type_controller.h"
#include <utility>
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "components/password_manager/core/browser/password_manager_features_util.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/sync/base/features.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/passphrase_enums.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/sync/model/model_type_controller_delegate.h"
namespace password_manager {
namespace {
#if BUILDFLAG(IS_IOS)
// Master kill switch that can be used to disable enabling PASSWORDS transport
// mode for users using non-standard encryption passphrase types (explicit
// passphrase or kTrustedVaultPassphrase). Note that this is necessary but not
// sufficient to enable PASSWORDS in transport mode.
BASE_FEATURE(kSyncAllowTransportModeWithNonStandardEncryption,
"SyncAllowTransportModeWithNonStandardEncryption",
base::FEATURE_ENABLED_BY_DEFAULT);
#endif // BUILDFLAG(IS_IOS)
} // namespace
PasswordModelTypeController::PasswordModelTypeController(
std::unique_ptr<syncer::ModelTypeControllerDelegate>
delegate_for_full_sync_mode,
std::unique_ptr<syncer::ModelTypeControllerDelegate>
delegate_for_transport_mode,
PrefService* pref_service,
signin::IdentityManager* identity_manager,
syncer::SyncService* sync_service)
: ModelTypeController(syncer::PASSWORDS,
std::move(delegate_for_full_sync_mode),
std::move(delegate_for_transport_mode)),
pref_service_(pref_service),
identity_manager_(identity_manager),
sync_service_(sync_service),
account_storage_settings_watcher_(
pref_service_,
sync_service_,
base::BindRepeating(
&PasswordModelTypeController::OnOptInStateMaybeChanged,
base::Unretained(this))) {
identity_manager_observation_.Observe(identity_manager_);
}
PasswordModelTypeController::~PasswordModelTypeController() = default;
void PasswordModelTypeController::LoadModels(
const syncer::ConfigureContext& configure_context,
const ModelLoadCallback& model_load_callback) {
DCHECK(CalledOnValidThread());
sync_service_observation_.Observe(sync_service_);
sync_mode_ = configure_context.sync_mode;
ModelTypeController::LoadModels(configure_context, model_load_callback);
}
void PasswordModelTypeController::Stop(syncer::SyncStopMetadataFate fate,
StopCallback callback) {
DCHECK(CalledOnValidThread());
sync_service_observation_.Reset();
// In transport-only mode, our storage is scoped to the Gaia account. That
// means it should be cleared if Sync is stopped for any reason (other than
// just browser shutdown). E.g. when switching to full-Sync mode, we don't
// want to end up with two copies of the passwords (one in the profile DB, one
// in the account DB).
if (sync_mode_ == syncer::SyncMode::kTransportOnly) {
fate = syncer::SyncStopMetadataFate::CLEAR_METADATA;
}
ModelTypeController::Stop(fate, std::move(callback));
}
syncer::DataTypeController::PreconditionState
PasswordModelTypeController::GetPreconditionState() const {
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
// If Sync-the-feature is enabled, then the user has opted in to that, and no
// additional opt-in is required here.
if (sync_service_->IsSyncFeatureEnabled() ||
sync_service_->IsLocalSyncEnabled()) {
return PreconditionState::kPreconditionsMet;
}
// If Sync-the-feature is *not* enabled, then password sync should only be
// turned on if the user has opted in to the account-scoped storage.
return features_util::IsOptedInForAccountStorage(pref_service_, sync_service_)
? PreconditionState::kPreconditionsMet
: PreconditionState::kMustStopAndClearData;
#else
// On Android and iOS, there is no explicit opt-in - instead the user's choice
// is handled via Sync's selected types (see `UserSelectableType`). So nothing
// to check here.
return PreconditionState::kPreconditionsMet;
#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
}
bool PasswordModelTypeController::ShouldRunInTransportOnlyMode() const {
if (!base::FeatureList::IsEnabled(features::kEnablePasswordsAccountStorage)) {
return false;
}
#if BUILDFLAG(IS_IOS)
// Non-standard passphrase types require UI support to deal with error cases.
// On iOS, these UI changes (for transport mode) are guarded behind
// kIndicateAccountStorageErrorInAccountCell.
if (sync_service_->GetUserSettings()->IsUsingExplicitPassphrase() ||
sync_service_->GetUserSettings()->GetPassphraseType() ==
syncer::PassphraseType::kTrustedVaultPassphrase) {
if (!base::FeatureList::IsEnabled(
syncer::kIndicateAccountStorageErrorInAccountCell) ||
!base::FeatureList::IsEnabled(
kSyncAllowTransportModeWithNonStandardEncryption)) {
return false;
}
}
#else
// Outside iOS, passphrase errors aren't reported in the UI, so it doesn't
// make sense to enable this datatype.
if (sync_service_->GetUserSettings()->IsUsingExplicitPassphrase()) {
return false;
}
#endif // BUILDFLAG(IS_IOS)
return true;
}
void PasswordModelTypeController::OnStateChanged(syncer::SyncService* sync) {
DCHECK(CalledOnValidThread());
sync_service_->DataTypePreconditionChanged(syncer::PASSWORDS);
}
void PasswordModelTypeController::OnAccountsInCookieUpdated(
const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
const GoogleServiceAuthError& error) {
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
// If the account information is stale, do nothing for now - wait until there
// is fresh information.
if (!accounts_in_cookie_jar_info.accounts_are_fresh) {
return;
}
// Collect all the known accounts (signed-in or signed-out).
std::vector<std::string> gaia_ids;
for (const gaia::ListedAccount& account :
accounts_in_cookie_jar_info.signed_in_accounts) {
gaia_ids.push_back(account.gaia_id);
}
for (const gaia::ListedAccount& account :
accounts_in_cookie_jar_info.signed_out_accounts) {
gaia_ids.push_back(account.gaia_id);
}
// Keep any account-storage settings only for known accounts.
features_util::KeepAccountStorageSettingsOnlyForUsers(pref_service_,
gaia_ids);
#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
}
void PasswordModelTypeController::OnAccountsCookieDeletedByUserAction() {
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
features_util::ClearAccountStorageSettingsForAllUsers(pref_service_);
#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
}
void PasswordModelTypeController::OnPrimaryAccountChanged(
const signin::PrimaryAccountChangeEvent& event) {
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
if (event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
signin::PrimaryAccountChangeEvent::Type::kCleared) {
// Note: kCleared event for ConsentLevel::kSync basically means that the
// consent for Sync-the-feature was revoked. In this case, also clear any
// possible matching opt-in for the account-scoped storage, since it'd
// probably be surprising to the user if their account passwords still
// remained after disabling Sync.
features_util::OptOutOfAccountStorageAndClearSettingsForAccount(
pref_service_, event.GetPreviousState().primary_account.gaia);
}
#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
}
void PasswordModelTypeController::OnOptInStateMaybeChanged() {
// Note: This method gets called in many other situations as well, not just
// when the opt-in state changes, but DataTypePreconditionChanged() is cheap
// if nothing actually changed, so some spurious calls don't hurt.
sync_service_->DataTypePreconditionChanged(syncer::PASSWORDS);
}
} // namespace password_manager