blob: 350d7d7cafa2c16bb868aa09ad4b9a39ffc36ea9 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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/password_store.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/debug/dump_without_crashing.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/android_affiliation/affiliated_match_helper.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_util.h"
#include "components/password_manager/core/browser/password_store_consumer.h"
#include "components/password_manager/core/browser/statistics_table.h"
#include "components/password_manager/core/browser/sync/password_sync_bridge.h"
#include "components/password_manager/core/browser/sync/password_syncable_service.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "components/sync/model_impl/client_tag_based_model_type_processor.h"
#include "components/sync/model_impl/proxy_model_type_controller_delegate.h"
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
#include "components/password_manager/core/browser/password_store_signin_notifier.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#endif
using autofill::PasswordForm;
namespace password_manager {
PasswordStore::GetLoginsRequest::GetLoginsRequest(
PasswordStoreConsumer* consumer)
: consumer_weak_(consumer->GetWeakPtr()) {
origin_task_runner_ = base::SequencedTaskRunnerHandle::Get();
}
PasswordStore::GetLoginsRequest::~GetLoginsRequest() {
}
void PasswordStore::GetLoginsRequest::NotifyConsumerWithResults(
std::vector<std::unique_ptr<PasswordForm>> results) {
if (!ignore_logins_cutoff_.is_null()) {
base::EraseIf(results,
[this](const std::unique_ptr<PasswordForm>& credential) {
return (credential->date_created < ignore_logins_cutoff_);
});
}
origin_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PasswordStoreConsumer::OnGetPasswordStoreResults,
consumer_weak_, std::move(results)));
}
void PasswordStore::GetLoginsRequest::NotifyWithSiteStatistics(
std::vector<InteractionsStats> stats) {
origin_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PasswordStoreConsumer::OnGetSiteStatistics,
consumer_weak_, std::move(stats)));
}
// TODO(crbug.com/706392): Fix password reuse detection for Android.
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
PasswordStore::CheckReuseRequest::CheckReuseRequest(
PasswordReuseDetectorConsumer* consumer)
: origin_task_runner_(base::SequencedTaskRunnerHandle::Get()),
consumer_weak_(consumer->AsWeakPtr()) {}
PasswordStore::CheckReuseRequest::~CheckReuseRequest() {}
void PasswordStore::CheckReuseRequest::OnReuseFound(
size_t password_length,
base::Optional<PasswordHashData> reused_protected_password_hash,
const std::vector<std::string>& matching_domains,
int saved_passwords) {
origin_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&PasswordReuseDetectorConsumer::OnReuseFound,
consumer_weak_, password_length,
reused_protected_password_hash,
matching_domains, saved_passwords));
}
#endif
PasswordStore::FormDigest::FormDigest(autofill::PasswordForm::Scheme new_scheme,
const std::string& new_signon_realm,
const GURL& new_origin)
: scheme(new_scheme), signon_realm(new_signon_realm), origin(new_origin) {}
PasswordStore::FormDigest::FormDigest(const PasswordForm& form)
: scheme(form.scheme),
signon_realm(form.signon_realm),
origin(form.origin) {}
PasswordStore::FormDigest::FormDigest(const autofill::FormData& form)
: scheme(PasswordForm::SCHEME_HTML),
signon_realm(form.origin.GetOrigin().spec()),
origin(form.origin) {}
PasswordStore::FormDigest::FormDigest(const FormDigest& other) = default;
PasswordStore::FormDigest::FormDigest(FormDigest&& other) = default;
PasswordStore::FormDigest& PasswordStore::FormDigest::operator=(
const FormDigest& other) = default;
PasswordStore::FormDigest& PasswordStore::FormDigest::operator=(
FormDigest&& other) = default;
bool PasswordStore::FormDigest::operator==(const FormDigest& other) const {
return scheme == other.scheme && signon_realm == other.signon_realm &&
origin == other.origin;
}
PasswordStore::PasswordStore()
: observers_(new base::ObserverListThreadSafe<Observer>()),
is_propagating_password_changes_to_web_credentials_enabled_(false),
shutdown_called_(false),
init_status_(InitStatus::kUnknown) {}
bool PasswordStore::Init(const syncer::SyncableService::StartSyncFlare& flare,
PrefService* prefs) {
main_task_runner_ = base::SequencedTaskRunnerHandle::Get();
DCHECK(main_task_runner_);
background_task_runner_ = CreateBackgroundTaskRunner();
DCHECK(background_task_runner_);
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
prefs_ = prefs;
hash_password_manager_.set_prefs(prefs);
#endif
if (background_task_runner_) {
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::BindOnce(&PasswordStore::InitOnBackgroundSequence, this, flare),
base::BindOnce(&PasswordStore::OnInitCompleted, this));
}
return true;
}
void PasswordStore::SetAffiliatedMatchHelper(
std::unique_ptr<AffiliatedMatchHelper> helper) {
affiliated_match_helper_ = std::move(helper);
}
void PasswordStore::AddLogin(const PasswordForm& form) {
ScheduleTask(base::BindOnce(&PasswordStore::AddLoginInternal, this, form));
}
void PasswordStore::UpdateLogin(const PasswordForm& form) {
ScheduleTask(base::BindOnce(&PasswordStore::UpdateLoginInternal, this, form));
}
void PasswordStore::UpdateLoginWithPrimaryKey(
const autofill::PasswordForm& new_form,
const autofill::PasswordForm& old_primary_key) {
ScheduleTask(base::BindOnce(&PasswordStore::UpdateLoginWithPrimaryKeyInternal,
this, new_form, old_primary_key));
}
void PasswordStore::RemoveLogin(const PasswordForm& form) {
ScheduleTask(base::BindOnce(&PasswordStore::RemoveLoginInternal, this, form));
}
void PasswordStore::RemoveLoginsByURLAndTime(
const base::Callback<bool(const GURL&)>& url_filter,
base::Time delete_begin,
base::Time delete_end,
const base::Closure& completion) {
ScheduleTask(base::BindOnce(&PasswordStore::RemoveLoginsByURLAndTimeInternal,
this, url_filter, delete_begin, delete_end,
completion));
}
void PasswordStore::RemoveLoginsCreatedBetween(
base::Time delete_begin,
base::Time delete_end,
const base::Closure& completion) {
ScheduleTask(
base::BindOnce(&PasswordStore::RemoveLoginsCreatedBetweenInternal, this,
delete_begin, delete_end, completion));
}
void PasswordStore::RemoveLoginsSyncedBetween(base::Time delete_begin,
base::Time delete_end) {
ScheduleTask(base::BindOnce(&PasswordStore::RemoveLoginsSyncedBetweenInternal,
this, delete_begin, delete_end));
}
void PasswordStore::RemoveStatisticsByOriginAndTime(
const base::Callback<bool(const GURL&)>& origin_filter,
base::Time delete_begin,
base::Time delete_end,
const base::Closure& completion) {
ScheduleTask(base::BindOnce(
&PasswordStore::RemoveStatisticsByOriginAndTimeInternal, this,
origin_filter, delete_begin, delete_end, completion));
}
void PasswordStore::DisableAutoSignInForOrigins(
const base::Callback<bool(const GURL&)>& origin_filter,
const base::Closure& completion) {
ScheduleTask(base::BindOnce(
&PasswordStore::DisableAutoSignInForOriginsInternal, this,
base::RepeatingCallback<bool(const GURL&)>(origin_filter), completion));
}
void PasswordStore::GetLogins(const FormDigest& form,
PasswordStoreConsumer* consumer) {
// Per http://crbug.com/121738, we deliberately ignore saved logins for
// http*://www.google.com/ that were stored prior to 2012. (Google now uses
// https://accounts.google.com/ for all login forms, so these should be
// unused.) We don't delete them just yet, and they'll still be visible in the
// password manager, but we won't use them to autofill any forms. This is a
// security feature to help minimize damage that can be done by XSS attacks.
// TODO(mdm): actually delete them at some point, say M24 or so.
base::Time ignore_logins_cutoff; // the null time
if (form.scheme == PasswordForm::SCHEME_HTML &&
(form.signon_realm == "http://www.google.com" ||
form.signon_realm == "http://www.google.com/" ||
form.signon_realm == "https://www.google.com" ||
form.signon_realm == "https://www.google.com/")) {
static const base::Time::Exploded exploded_cutoff =
{ 2012, 1, 0, 1, 0, 0, 0, 0 }; // 00:00 Jan 1 2012
base::Time out_time;
bool conversion_success =
base::Time::FromUTCExploded(exploded_cutoff, &out_time);
DCHECK(conversion_success);
ignore_logins_cutoff = out_time;
}
std::unique_ptr<GetLoginsRequest> request(new GetLoginsRequest(consumer));
request->set_ignore_logins_cutoff(ignore_logins_cutoff);
if (affiliated_match_helper_) {
affiliated_match_helper_->GetAffiliatedAndroidRealms(
form, base::Bind(&PasswordStore::ScheduleGetLoginsWithAffiliations,
this, form, base::Passed(&request)));
} else {
ScheduleTask(base::BindOnce(&PasswordStore::GetLoginsImpl, this, form,
base::Passed(&request)));
}
}
void PasswordStore::GetLoginsForSameOrganizationName(
const std::string& signon_realm,
PasswordStoreConsumer* consumer) {
std::unique_ptr<GetLoginsRequest> request(new GetLoginsRequest(consumer));
ScheduleTask(
base::BindOnce(&PasswordStore::GetLoginsForSameOrganizationNameImpl, this,
signon_realm, base::Passed(&request)));
}
void PasswordStore::GetAutofillableLogins(PasswordStoreConsumer* consumer) {
Schedule(&PasswordStore::GetAutofillableLoginsImpl, consumer);
}
void PasswordStore::GetAutofillableLoginsWithAffiliationAndBrandingInformation(
PasswordStoreConsumer* consumer) {
Schedule(&PasswordStore::
GetAutofillableLoginsWithAffiliationAndBrandingInformationImpl,
consumer);
}
void PasswordStore::GetBlacklistLogins(PasswordStoreConsumer* consumer) {
Schedule(&PasswordStore::GetBlacklistLoginsImpl, consumer);
}
void PasswordStore::GetBlacklistLoginsWithAffiliationAndBrandingInformation(
PasswordStoreConsumer* consumer) {
Schedule(&PasswordStore::
GetBlacklistLoginsWithAffiliationAndBrandingInformationImpl,
consumer);
}
void PasswordStore::GetAllLoginsWithAffiliationAndBrandingInformation(
PasswordStoreConsumer* consumer) {
Schedule(
&PasswordStore::GetAllLoginsWithAffiliationAndBrandingInformationImpl,
consumer);
}
void PasswordStore::ReportMetrics(const std::string& sync_username,
bool custom_passphrase_sync_enabled,
bool is_under_advanced_protection) {
if (background_task_runner_) {
base::Closure task =
base::Bind(&PasswordStore::ReportMetricsImpl, this, sync_username,
custom_passphrase_sync_enabled);
background_task_runner_->PostDelayedTask(FROM_HERE, task,
base::TimeDelta::FromSeconds(30));
}
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
if (!sync_username.empty()) {
auto hash_password_state =
hash_password_manager_.HasPasswordHash(sync_username,
/*is_gaia_password=*/true)
? metrics_util::IsSyncPasswordHashSaved::SAVED_VIA_LIST_PREF
: metrics_util::IsSyncPasswordHashSaved::NOT_SAVED;
metrics_util::LogIsSyncPasswordHashSaved(hash_password_state,
is_under_advanced_protection);
}
#endif
}
void PasswordStore::AddSiteStats(const InteractionsStats& stats) {
ScheduleTask(base::BindOnce(&PasswordStore::AddSiteStatsImpl, this, stats));
}
void PasswordStore::RemoveSiteStats(const GURL& origin_domain) {
ScheduleTask(
base::BindOnce(&PasswordStore::RemoveSiteStatsImpl, this, origin_domain));
}
void PasswordStore::GetAllSiteStats(PasswordStoreConsumer* consumer) {
std::unique_ptr<GetLoginsRequest> request(new GetLoginsRequest(consumer));
ScheduleTask(base::BindOnce(&PasswordStore::NotifyAllSiteStats, this,
base::Passed(&request)));
}
void PasswordStore::GetSiteStats(const GURL& origin_domain,
PasswordStoreConsumer* consumer) {
std::unique_ptr<GetLoginsRequest> request(new GetLoginsRequest(consumer));
ScheduleTask(base::BindOnce(&PasswordStore::NotifySiteStats, this,
origin_domain, base::Passed(&request)));
}
void PasswordStore::AddObserver(Observer* observer) {
observers_->AddObserver(observer);
}
void PasswordStore::RemoveObserver(Observer* observer) {
observers_->RemoveObserver(observer);
}
bool PasswordStore::ScheduleTask(base::OnceClosure task) {
if (background_task_runner_)
return background_task_runner_->PostTask(FROM_HERE, std::move(task));
return false;
}
scoped_refptr<base::SequencedTaskRunner>
PasswordStore::GetBackgroundTaskRunner() {
return background_task_runner_;
}
bool PasswordStore::IsAbleToSavePasswords() const {
return init_status_ == InitStatus::kSuccess;
}
void PasswordStore::ShutdownOnUIThread() {
ScheduleTask(base::Bind(&PasswordStore::DestroyOnBackgroundSequence, this));
// The AffiliationService must be destroyed from the main sequence.
affiliated_match_helper_.reset();
shutdown_called_ = true;
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
if (notifier_)
notifier_->UnsubscribeFromSigninEvents();
#endif
}
base::WeakPtr<syncer::SyncableService>
PasswordStore::GetPasswordSyncableService() {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
DCHECK(syncable_service_);
return syncable_service_->AsWeakPtr();
}
std::unique_ptr<syncer::ProxyModelTypeControllerDelegate>
PasswordStore::CreateSyncControllerDelegate() {
DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
// Note that a callback is bound for
// GetSyncControllerDelegateOnBackgroundSequence() because this getter itself
// must also run in the backend sequence, and the proxy object below will take
// care of that.
return std::make_unique<syncer::ProxyModelTypeControllerDelegate>(
background_task_runner_,
base::BindRepeating(
&PasswordStore::GetSyncControllerDelegateOnBackgroundSequence,
base::Unretained(this)));
}
// TODO(crbug.com/706392): Fix password reuse detection for Android.
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
void PasswordStore::CheckReuse(const base::string16& input,
const std::string& domain,
PasswordReuseDetectorConsumer* consumer) {
auto check_reuse_request = std::make_unique<CheckReuseRequest>(consumer);
ScheduleTask(base::Bind(&PasswordStore::CheckReuseImpl, this,
base::Passed(&check_reuse_request), input, domain));
}
#endif
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
void PasswordStore::PreparePasswordHashData(const std::string& sync_username) {
SchedulePasswordHashUpdate(/*should_log_metrics=*/true);
ScheduleEnterprisePasswordURLUpdate();
}
void PasswordStore::SaveGaiaPasswordHash(
const std::string& username,
const base::string16& password,
metrics_util::SyncPasswordHashChange event) {
SaveProtectedPasswordHash(username, password, /*is_gaia_password=*/true,
event);
}
void PasswordStore::SaveEnterprisePasswordHash(const std::string& username,
const base::string16& password) {
SaveProtectedPasswordHash(
username, password, /*is_gaia_password=*/false,
metrics_util::SyncPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE);
}
void PasswordStore::SaveProtectedPasswordHash(
const std::string& username,
const base::string16& password,
bool is_gaia_password,
metrics_util::SyncPasswordHashChange event) {
if (hash_password_manager_.SavePasswordHash(username, password,
is_gaia_password)) {
if (is_gaia_password &&
event !=
metrics_util::SyncPasswordHashChange::NOT_SYNC_PASSWORD_CHANGE) {
metrics_util::LogSyncPasswordHashChange(event);
}
SchedulePasswordHashUpdate(/*should_log_metrics=*/false);
}
}
void PasswordStore::SaveSyncPasswordHash(
const PasswordHashData& sync_password_data,
metrics_util::SyncPasswordHashChange event) {
if (hash_password_manager_.SavePasswordHash(sync_password_data)) {
metrics_util::LogSyncPasswordHashChange(event);
SchedulePasswordHashUpdate(/*should_log_metrics=*/false);
}
}
void PasswordStore::ClearGaiaPasswordHash(const std::string& username) {
hash_password_manager_.ClearSavedPasswordHash(username,
/*is_gaia_password=*/true);
ScheduleTask(base::BindRepeating(&PasswordStore::ClearGaiaPasswordHashImpl,
this, username));
}
void PasswordStore::ClearAllGaiaPasswordHash() {
hash_password_manager_.ClearAllPasswordHash(/* is_gaia_password= */ true);
ScheduleTask(
base::BindRepeating(&PasswordStore::ClearAllGaiaPasswordHashImpl, this));
}
void PasswordStore::ClearAllEnterprisePasswordHash() {
hash_password_manager_.ClearAllPasswordHash(/* is_gaia_password= */ false);
ScheduleTask(base::BindRepeating(
&PasswordStore::ClearAllEnterprisePasswordHashImpl, this));
}
void PasswordStore::SetPasswordStoreSigninNotifier(
std::unique_ptr<PasswordStoreSigninNotifier> notifier) {
DCHECK(!notifier_);
DCHECK(notifier);
notifier_ = std::move(notifier);
notifier_->SubscribeToSigninEvents(this);
}
void PasswordStore::SchedulePasswordHashUpdate(bool should_log_metrics) {
ScheduleTask(base::BindRepeating(
&PasswordStore::SaveProtectedPasswordHashImpl, this,
base::Passed(hash_password_manager_.RetrieveAllPasswordHashes()),
should_log_metrics));
}
void PasswordStore::ScheduleEnterprisePasswordURLUpdate() {
std::vector<GURL> enterprise_login_urls;
safe_browsing::GetPasswordProtectionLoginURLsPref(*prefs_,
&enterprise_login_urls);
GURL enterprise_change_password_url =
safe_browsing::GetPasswordProtectionChangePasswordURLPref(*prefs_);
ScheduleTask(base::BindRepeating(&PasswordStore::SaveEnterprisePasswordURLs,
this, base::Passed(&enterprise_login_urls),
enterprise_change_password_url));
}
#endif
PasswordStore::~PasswordStore() {
DCHECK(shutdown_called_);
}
scoped_refptr<base::SequencedTaskRunner>
PasswordStore::CreateBackgroundTaskRunner() const {
return base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE});
}
bool PasswordStore::InitOnBackgroundSequence(
const syncer::SyncableService::StartSyncFlare& flare) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
if (base::FeatureList::IsEnabled(switches::kSyncUSSPasswords)) {
sync_bridge_.reset(new PasswordSyncBridge(
std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
syncer::PASSWORDS, base::DoNothing())));
} else {
DCHECK(!syncable_service_);
syncable_service_.reset(new PasswordSyncableService(this));
syncable_service_->InjectStartSyncFlare(flare);
}
// TODO(crbug.com/706392): Fix password reuse detection for Android.
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
reuse_detector_ = new PasswordReuseDetector;
GetAutofillableLoginsImpl(
std::make_unique<GetLoginsRequest>(reuse_detector_));
#endif
return true;
}
void PasswordStore::GetLoginsImpl(const FormDigest& form,
std::unique_ptr<GetLoginsRequest> request) {
SCOPED_UMA_HISTOGRAM_TIMER("PasswordManager.StorePerformance.GetLogins");
request->NotifyConsumerWithResults(FillMatchingLogins(form));
}
void PasswordStore::LogStatsForBulkDeletion(int num_deletions) {
UMA_HISTOGRAM_COUNTS_1M("PasswordManager.NumPasswordsDeletedByBulkDelete",
num_deletions);
}
void PasswordStore::LogStatsForBulkDeletionDuringRollback(int num_deletions) {
UMA_HISTOGRAM_COUNTS_1M("PasswordManager.NumPasswordsDeletedDuringRollback",
num_deletions);
}
PasswordStoreChangeList PasswordStore::AddLoginSync(const PasswordForm& form) {
// There is no good way to check if the password is actually up to date, or
// at least to check if it was actually changed. Assume it is.
if (AffiliatedMatchHelper::IsValidAndroidCredential(
PasswordStore::FormDigest(form)))
ScheduleFindAndUpdateAffiliatedWebLogins(form);
return AddLoginImpl(form);
}
PasswordStoreChangeList PasswordStore::UpdateLoginSync(
const PasswordForm& form) {
if (AffiliatedMatchHelper::IsValidAndroidCredential(
PasswordStore::FormDigest(form))) {
// Ideally, a |form| would not be updated in any way unless it was ensured
// that it, as a whole, can be used for a successful login. This, sadly, can
// not be guaranteed. It might be that |form| just contains updates to some
// meta-attribute, while it still has an out-of-date password. If such a
// password were to be propagated to affiliated credentials in that case, it
// may very well overwrite the actual, up-to-date password. Try to mitigate
// this risk by ignoring updates unless they actually update the password.
std::unique_ptr<PasswordForm> old_form(GetLoginImpl(form));
if (old_form && form.password_value != old_form->password_value)
ScheduleFindAndUpdateAffiliatedWebLogins(form);
}
return UpdateLoginImpl(form);
}
PasswordStoreChangeList PasswordStore::RemoveLoginSync(
const PasswordForm& form) {
return RemoveLoginImpl(form);
}
void PasswordStore::NotifyLoginsChanged(
const PasswordStoreChangeList& changes) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
if (!changes.empty()) {
observers_->Notify(FROM_HERE, &Observer::OnLoginsChanged, changes);
if (syncable_service_)
syncable_service_->ActOnPasswordStoreChanges(changes);
if (sync_bridge_)
sync_bridge_->ActOnPasswordStoreChanges(changes);
// TODO(crbug.com/706392): Fix password reuse detection for Android.
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
if (reuse_detector_)
reuse_detector_->OnLoginsChanged(changes);
#endif
}
}
// TODO(crbug.com/706392): Fix password reuse detection for Android.
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
void PasswordStore::CheckReuseImpl(std::unique_ptr<CheckReuseRequest> request,
const base::string16& input,
const std::string& domain) {
if (reuse_detector_) {
reuse_detector_->CheckReuse(input, domain, request.get());
}
}
void PasswordStore::SaveProtectedPasswordHashImpl(
PasswordHashDataList protected_password_data_list,
bool should_log_metrics) {
if (!reuse_detector_ || !protected_password_data_list.has_value())
return;
std::vector<PasswordHashData> gaia_password_hash_list;
std::vector<PasswordHashData> enterprise_password_hash_list;
for (PasswordHashData& password_hash : *protected_password_data_list) {
if (password_hash.is_gaia_password)
gaia_password_hash_list.push_back(std::move(password_hash));
else
enterprise_password_hash_list.push_back(std::move(password_hash));
}
if (should_log_metrics) {
metrics_util::LogProtectedPasswordHashCounts(
gaia_password_hash_list.size(), enterprise_password_hash_list.size());
}
reuse_detector_->UseGaiaPasswordHash(std::move(gaia_password_hash_list));
reuse_detector_->UseNonGaiaEnterprisePasswordHash(
std::move(enterprise_password_hash_list));
}
void PasswordStore::SaveEnterprisePasswordURLs(
const std::vector<GURL>& enterprise_login_urls,
const GURL& enterprise_change_password_url) {
if (!reuse_detector_)
return;
reuse_detector_->UseEnterprisePasswordURLs(std::move(enterprise_login_urls),
enterprise_change_password_url);
}
void PasswordStore::ClearGaiaPasswordHashImpl(const std::string& username) {
if (reuse_detector_)
reuse_detector_->ClearGaiaPasswordHash(username);
}
void PasswordStore::ClearAllGaiaPasswordHashImpl() {
if (reuse_detector_)
reuse_detector_->ClearAllGaiaPasswordHash();
}
void PasswordStore::ClearAllEnterprisePasswordHashImpl() {
if (reuse_detector_)
reuse_detector_->ClearAllEnterprisePasswordHash();
}
#endif
void PasswordStore::OnInitCompleted(bool success) {
DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
init_status_ = success ? InitStatus::kSuccess : InitStatus::kFailure;
UMA_HISTOGRAM_BOOLEAN("PasswordManager.PasswordStoreInitResult", success);
}
void PasswordStore::Schedule(
void (PasswordStore::*func)(std::unique_ptr<GetLoginsRequest>),
PasswordStoreConsumer* consumer) {
std::unique_ptr<GetLoginsRequest> request(new GetLoginsRequest(consumer));
consumer->cancelable_task_tracker()->PostTask(
background_task_runner_.get(), FROM_HERE,
base::BindOnce(func, this, std::move(request)));
}
void PasswordStore::AddLoginInternal(const PasswordForm& form) {
SCOPED_UMA_HISTOGRAM_TIMER("PasswordManager.StorePerformance.AddLogin");
BeginTransaction();
PasswordStoreChangeList changes = AddLoginImpl(form);
NotifyLoginsChanged(changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
}
void PasswordStore::UpdateLoginInternal(const PasswordForm& form) {
SCOPED_UMA_HISTOGRAM_TIMER("PasswordManager.StorePerformance.UpdateLogin");
BeginTransaction();
PasswordStoreChangeList changes = UpdateLoginImpl(form);
NotifyLoginsChanged(changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
}
void PasswordStore::RemoveLoginInternal(const PasswordForm& form) {
SCOPED_UMA_HISTOGRAM_TIMER("PasswordManager.StorePerformance.RemoveLogin");
BeginTransaction();
PasswordStoreChangeList changes = RemoveLoginImpl(form);
NotifyLoginsChanged(changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
}
void PasswordStore::UpdateLoginWithPrimaryKeyInternal(
const PasswordForm& new_form,
const PasswordForm& old_primary_key) {
BeginTransaction();
PasswordStoreChangeList all_changes = RemoveLoginImpl(old_primary_key);
PasswordStoreChangeList changes = AddLoginImpl(new_form);
all_changes.insert(all_changes.end(), changes.begin(), changes.end());
NotifyLoginsChanged(all_changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
}
void PasswordStore::RemoveLoginsByURLAndTimeInternal(
const base::Callback<bool(const GURL&)>& url_filter,
base::Time delete_begin,
base::Time delete_end,
const base::Closure& completion) {
BeginTransaction();
PasswordStoreChangeList changes =
RemoveLoginsByURLAndTimeImpl(url_filter, delete_begin, delete_end);
NotifyLoginsChanged(changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
if (!completion.is_null())
main_task_runner_->PostTask(FROM_HERE, completion);
}
void PasswordStore::RemoveLoginsCreatedBetweenInternal(
base::Time delete_begin,
base::Time delete_end,
const base::Closure& completion) {
BeginTransaction();
PasswordStoreChangeList changes =
RemoveLoginsCreatedBetweenImpl(delete_begin, delete_end);
NotifyLoginsChanged(changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
if (!completion.is_null())
main_task_runner_->PostTask(FROM_HERE, completion);
}
void PasswordStore::RemoveLoginsSyncedBetweenInternal(base::Time delete_begin,
base::Time delete_end) {
BeginTransaction();
PasswordStoreChangeList changes =
RemoveLoginsSyncedBetweenImpl(delete_begin, delete_end);
NotifyLoginsChanged(changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
}
void PasswordStore::RemoveStatisticsByOriginAndTimeInternal(
const base::Callback<bool(const GURL&)>& origin_filter,
base::Time delete_begin,
base::Time delete_end,
const base::Closure& completion) {
RemoveStatisticsByOriginAndTimeImpl(origin_filter, delete_begin, delete_end);
if (!completion.is_null())
main_task_runner_->PostTask(FROM_HERE, completion);
}
void PasswordStore::DisableAutoSignInForOriginsInternal(
const base::Callback<bool(const GURL&)>& origin_filter,
const base::Closure& completion) {
DisableAutoSignInForOriginsImpl(origin_filter);
if (!completion.is_null())
main_task_runner_->PostTask(FROM_HERE, completion);
}
void PasswordStore::GetLoginsForSameOrganizationNameImpl(
const std::string& signon_realm,
std::unique_ptr<GetLoginsRequest> request) {
request->NotifyConsumerWithResults(
FillLoginsForSameOrganizationName(signon_realm));
}
void PasswordStore::GetAutofillableLoginsImpl(
std::unique_ptr<GetLoginsRequest> request) {
std::vector<std::unique_ptr<PasswordForm>> obtained_forms;
if (!FillAutofillableLogins(&obtained_forms))
obtained_forms.clear();
request->NotifyConsumerWithResults(std::move(obtained_forms));
}
void PasswordStore::
GetAutofillableLoginsWithAffiliationAndBrandingInformationImpl(
std::unique_ptr<GetLoginsRequest> request) {
std::vector<std::unique_ptr<PasswordForm>> obtained_forms;
if (!FillAutofillableLogins(&obtained_forms))
obtained_forms.clear();
// Since AffiliatedMatchHelper's requests should be sent from UI thread,
// post a request to UI thread.
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PasswordStore::InjectAffiliationAndBrandingInformation,
this, std::move(obtained_forms), std::move(request)));
}
void PasswordStore::GetBlacklistLoginsImpl(
std::unique_ptr<GetLoginsRequest> request) {
std::vector<std::unique_ptr<PasswordForm>> obtained_forms;
if (!FillBlacklistLogins(&obtained_forms))
obtained_forms.clear();
request->NotifyConsumerWithResults(std::move(obtained_forms));
}
void PasswordStore::GetBlacklistLoginsWithAffiliationAndBrandingInformationImpl(
std::unique_ptr<GetLoginsRequest> request) {
std::vector<std::unique_ptr<PasswordForm>> obtained_forms;
if (!FillBlacklistLogins(&obtained_forms))
obtained_forms.clear();
// Since AffiliatedMatchHelper's requests should be sent from UI thread,
// post a request to UI thread.
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PasswordStore::InjectAffiliationAndBrandingInformation,
this, std::move(obtained_forms), std::move(request)));
}
void PasswordStore::GetAllLoginsWithAffiliationAndBrandingInformationImpl(
std::unique_ptr<GetLoginsRequest> request) {
std::vector<std::unique_ptr<PasswordForm>> results;
for (auto fill_logins : {&PasswordStore::FillAutofillableLogins,
&PasswordStore::FillBlacklistLogins}) {
std::vector<std::unique_ptr<PasswordForm>> obtained_forms;
if ((this->*fill_logins)(&obtained_forms)) {
results.insert(results.end(),
std::make_move_iterator(obtained_forms.begin()),
std::make_move_iterator(obtained_forms.end()));
}
}
// Since AffiliatedMatchHelper's requests should be sent from UI thread,
// post a request to UI thread.
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PasswordStore::InjectAffiliationAndBrandingInformation,
this, std::move(results), std::move(request)));
}
void PasswordStore::NotifyAllSiteStats(
std::unique_ptr<GetLoginsRequest> request) {
request->NotifyWithSiteStatistics(GetAllSiteStatsImpl());
}
void PasswordStore::NotifySiteStats(const GURL& origin_domain,
std::unique_ptr<GetLoginsRequest> request) {
request->NotifyWithSiteStatistics(GetSiteStatsImpl(origin_domain));
}
void PasswordStore::GetLoginsWithAffiliationsImpl(
const FormDigest& form,
std::unique_ptr<GetLoginsRequest> request,
const std::vector<std::string>& additional_android_realms) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
std::vector<std::unique_ptr<PasswordForm>> results(FillMatchingLogins(form));
for (const std::string& realm : additional_android_realms) {
std::vector<std::unique_ptr<PasswordForm>> more_results(
FillMatchingLogins({PasswordForm::SCHEME_HTML, realm, GURL()}));
for (auto& result : more_results)
result->is_affiliation_based_match = true;
password_manager_util::TrimUsernameOnlyCredentials(&more_results);
const size_t results_count = results.size();
results.resize(results_count + more_results.size());
std::move(more_results.begin(), more_results.end(),
results.begin() + results_count);
}
request->NotifyConsumerWithResults(std::move(results));
}
void PasswordStore::InjectAffiliationAndBrandingInformation(
std::vector<std::unique_ptr<PasswordForm>> forms,
std::unique_ptr<GetLoginsRequest> request) {
if (affiliated_match_helper_) {
affiliated_match_helper_->InjectAffiliationAndBrandingInformation(
std::move(forms),
base::Bind(&PasswordStore::GetLoginsRequest::NotifyConsumerWithResults,
base::Owned(request.release())));
} else {
request->NotifyConsumerWithResults(std::move(forms));
}
}
void PasswordStore::ScheduleGetLoginsWithAffiliations(
const FormDigest& form,
std::unique_ptr<GetLoginsRequest> request,
const std::vector<std::string>& additional_android_realms) {
ScheduleTask(base::Bind(&PasswordStore::GetLoginsWithAffiliationsImpl, this,
form, base::Passed(&request),
additional_android_realms));
}
std::unique_ptr<PasswordForm> PasswordStore::GetLoginImpl(
const PasswordForm& primary_key) {
SCOPED_UMA_HISTOGRAM_TIMER("PasswordManager.StorePerformance.GetLogin");
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
std::vector<std::unique_ptr<PasswordForm>> candidates(
FillMatchingLogins(FormDigest(primary_key)));
for (auto& candidate : candidates) {
if (ArePasswordFormUniqueKeyEqual(*candidate, primary_key) &&
!candidate->is_public_suffix_match) {
return std::move(candidate);
}
}
return nullptr;
}
void PasswordStore::FindAndUpdateAffiliatedWebLogins(
const PasswordForm& added_or_updated_android_form) {
if (!affiliated_match_helper_ ||
!is_propagating_password_changes_to_web_credentials_enabled_) {
return;
}
affiliated_match_helper_->GetAffiliatedWebRealms(
PasswordStore::FormDigest(added_or_updated_android_form),
base::Bind(&PasswordStore::ScheduleUpdateAffiliatedWebLoginsImpl, this,
added_or_updated_android_form));
}
void PasswordStore::ScheduleFindAndUpdateAffiliatedWebLogins(
const PasswordForm& added_or_updated_android_form) {
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PasswordStore::FindAndUpdateAffiliatedWebLogins, this,
added_or_updated_android_form));
}
void PasswordStore::UpdateAffiliatedWebLoginsImpl(
const PasswordForm& updated_android_form,
const std::vector<std::string>& affiliated_web_realms) {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
BeginTransaction();
PasswordStoreChangeList all_changes;
for (const std::string& affiliated_web_realm : affiliated_web_realms) {
std::vector<std::unique_ptr<PasswordForm>> web_logins(FillMatchingLogins(
{PasswordForm::SCHEME_HTML, affiliated_web_realm, GURL()}));
for (auto& web_login : web_logins) {
// Do not update HTTP logins, logins saved under insecure conditions, and
// non-HTML login forms; PSL matches; logins with a different username;
// and logins with the same password (to avoid generating no-op updates).
if (!AffiliatedMatchHelper::IsValidWebCredential(
FormDigest(*web_login)) ||
web_login->is_public_suffix_match ||
web_login->username_value != updated_android_form.username_value ||
web_login->password_value == updated_android_form.password_value)
continue;
// If the |web_login| was updated in the same or a later chunk of Sync
// changes, assume that it is more recent and do not update it. Note that
// this check is far from perfect conflict resolution and mostly prevents
// long-dormant Sync clients doing damage when they wake up in the face
// of the following list of changes:
//
// Time Source Change
// ==== ====== ======
// #1 Android android_login.password_value = "A"
// #2 Client A web_login.password_value = "A" (propagation)
// #3 Client A web_login.password_value = "B" (manual overwrite)
//
// When long-dormant Sync client B wakes up, it will only get a distilled
// subset of not-yet-obsoleted changes {1, 3}. In this case, client B must
// not propagate password "A" to |web_login|. This is prevented as change
// #3 will arrive either in the same/later chunk of sync changes, so the
// |date_synced| of |web_login| value will be greater or equal.
//
// Note that this solution has several shortcomings:
//
// (1) It will not prevent local changes to |web_login| from being
// overwritten if they were made shortly after start-up, before
// Sync changes are applied. This should be tolerable.
//
// (2) It assumes that all Sync clients are fully capable of propagating
// changes to web credentials on their own. If client C runs an
// older version of Chrome and updates the password for |web_login|
// around the time when the |android_login| is updated, the updated
// password will not be propagated by client B to |web_login| when
// it wakes up, regardless of the temporal order of the original
// changes, as client B will see both credentials having the same
// |data_synced|.
//
// (2a) Above could be mitigated by looking not only at |data_synced|,
// but also at the actual order of Sync changes.
//
// (2b) However, (2a) is still not workable, as a Sync change is made
// when any attribute of the credential is updated, not only the
// password. Hence it is not possible for client B to distinguish
// between two following two event orders:
//
// #1 Android android_login.password_value = "A"
// #2 Client C web_login.password_value = "B" (manual overwrite)
// #3 Android android_login.random_attribute = "..."
//
// #1 Client C web_login.password_value = "B" (manual overwrite)
// #2 Android android_login.password_value = "A"
//
// And so it must assume that it is unsafe to update |web_login|.
if (web_login->date_synced >= updated_android_form.date_synced)
continue;
web_login->password_value = updated_android_form.password_value;
PasswordStoreChangeList changes = UpdateLoginImpl(*web_login);
all_changes.insert(all_changes.end(), changes.begin(), changes.end());
}
}
NotifyLoginsChanged(all_changes);
// Sync metadata get updated in NotifyLoginsChanged(). Therefore,
// CommitTransaction() must be called after NotifyLoginsChanged(), because
// sync codebase needs to update metadata atomically together with the login
// data.
CommitTransaction();
}
void PasswordStore::ScheduleUpdateAffiliatedWebLoginsImpl(
const PasswordForm& updated_android_form,
const std::vector<std::string>& affiliated_web_realms) {
ScheduleTask(base::Bind(&PasswordStore::UpdateAffiliatedWebLoginsImpl, this,
updated_android_form, affiliated_web_realms));
}
base::WeakPtr<syncer::ModelTypeControllerDelegate>
PasswordStore::GetSyncControllerDelegateOnBackgroundSequence() {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
DCHECK(sync_bridge_);
return sync_bridge_->change_processor()->GetControllerDelegate();
}
void PasswordStore::DestroyOnBackgroundSequence() {
DCHECK(background_task_runner_->RunsTasksInCurrentSequence());
syncable_service_.reset();
sync_bridge_.reset();
// TODO(crbug.com/706392): Fix password reuse detection for Android.
#if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
delete reuse_detector_;
reuse_detector_ = nullptr;
#endif
}
std::ostream& operator<<(std::ostream& os,
const PasswordStore::FormDigest& digest) {
return os << "FormDigest(scheme: " << digest.scheme
<< ", signon_realm: " << digest.signon_realm
<< ", origin: " << digest.origin << ")";
}
} // namespace password_manager