| // 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/test_password_store.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| |
| #include "base/check_op.h" |
| #include "base/containers/cxx20_erase.h" |
| #include "base/notreached.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "components/password_manager/core/browser/insecure_credentials_table.h" |
| #include "components/password_manager/core/browser/login_database.h" |
| #include "components/password_manager/core/browser/password_form.h" |
| #include "components/password_manager/core/browser/psl_matching_helper.h" |
| #include "components/password_manager/core/browser/statistics_table.h" |
| #include "components/sync/protocol/entity_metadata.pb.h" |
| #include "components/sync/protocol/model_type_state.pb.h" |
| #include "url/gurl.h" |
| |
| namespace password_manager { |
| |
| namespace { |
| |
| class TestPasswordSyncMetadataStore : public PasswordStoreSync::MetadataStore { |
| public: |
| TestPasswordSyncMetadataStore() = default; |
| ~TestPasswordSyncMetadataStore() override = default; |
| |
| // PasswordStoreSync::MetadataStore interface. |
| bool UpdateSyncMetadata(syncer::ModelType model_type, |
| const std::string& storage_key, |
| const sync_pb::EntityMetadata& metadata) override; |
| bool ClearSyncMetadata(syncer::ModelType model_type, |
| const std::string& storage_key) override; |
| bool UpdateModelTypeState( |
| syncer::ModelType model_type, |
| const sync_pb::ModelTypeState& model_type_state) override; |
| bool ClearModelTypeState(syncer::ModelType model_type) override; |
| std::unique_ptr<syncer::MetadataBatch> GetAllSyncMetadata() override; |
| void DeleteAllSyncMetadata() override; |
| void SetDeletionsHaveSyncedCallback( |
| base::RepeatingCallback<void(bool)> callback) override; |
| bool HasUnsyncedDeletions() override; |
| |
| private: |
| sync_pb::ModelTypeState sync_model_type_state_; |
| std::map<std::string, sync_pb::EntityMetadata> sync_metadata_; |
| }; |
| |
| bool TestPasswordSyncMetadataStore::UpdateSyncMetadata( |
| syncer::ModelType model_type, |
| const std::string& storage_key, |
| const sync_pb::EntityMetadata& metadata) { |
| DCHECK_EQ(model_type, syncer::PASSWORDS); |
| sync_metadata_[storage_key] = metadata; |
| return true; |
| } |
| |
| bool TestPasswordSyncMetadataStore::ClearSyncMetadata( |
| syncer::ModelType model_type, |
| const std::string& storage_key) { |
| sync_metadata_.erase(storage_key); |
| return true; |
| } |
| |
| bool TestPasswordSyncMetadataStore::UpdateModelTypeState( |
| syncer::ModelType model_type, |
| const sync_pb::ModelTypeState& model_type_state) { |
| DCHECK_EQ(model_type, syncer::PASSWORDS); |
| sync_model_type_state_ = model_type_state; |
| return true; |
| } |
| |
| bool TestPasswordSyncMetadataStore::ClearModelTypeState( |
| syncer::ModelType model_type) { |
| DCHECK_EQ(model_type, syncer::PASSWORDS); |
| sync_model_type_state_ = sync_pb::ModelTypeState(); |
| return true; |
| } |
| |
| std::unique_ptr<syncer::MetadataBatch> |
| TestPasswordSyncMetadataStore::GetAllSyncMetadata() { |
| auto metadata_batch = std::make_unique<syncer::MetadataBatch>(); |
| for (const auto& storage_key_and_metadata : sync_metadata_) { |
| metadata_batch->AddMetadata(storage_key_and_metadata.first, |
| std::make_unique<sync_pb::EntityMetadata>( |
| storage_key_and_metadata.second)); |
| } |
| metadata_batch->SetModelTypeState(sync_model_type_state_); |
| return metadata_batch; |
| } |
| |
| void TestPasswordSyncMetadataStore::DeleteAllSyncMetadata() { |
| ClearModelTypeState(syncer::PASSWORDS); |
| sync_metadata_.clear(); |
| } |
| |
| void TestPasswordSyncMetadataStore::SetDeletionsHaveSyncedCallback( |
| base::RepeatingCallback<void(bool)> callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool TestPasswordSyncMetadataStore::HasUnsyncedDeletions() { |
| return false; |
| } |
| |
| } // namespace |
| |
| TestPasswordStore::TestPasswordStore( |
| password_manager::IsAccountStore is_account_store) |
| : is_account_store_(is_account_store), |
| metadata_store_(std::make_unique<TestPasswordSyncMetadataStore>()) { |
| backend_ = this; |
| } |
| |
| const TestPasswordStore::PasswordMap& TestPasswordStore::stored_passwords() |
| const { |
| return stored_passwords_; |
| } |
| |
| void TestPasswordStore::Clear() { |
| stored_passwords_.clear(); |
| } |
| |
| bool TestPasswordStore::IsEmpty() { |
| // The store is empty, if the sum of all stored passwords across all entries |
| // in |stored_passwords_| is 0. |
| size_t number_of_passwords = 0u; |
| for (auto it = stored_passwords_.begin(); |
| !number_of_passwords && it != stored_passwords_.end(); ++it) { |
| number_of_passwords += it->second.size(); |
| } |
| return number_of_passwords == 0u; |
| } |
| |
| base::WeakPtr<syncer::ModelTypeControllerDelegate> |
| TestPasswordStore::GetSyncControllerDelegateOnBackgroundSequence() { |
| NOTIMPLEMENTED(); |
| return nullptr; |
| } |
| |
| TestPasswordStore::~TestPasswordStore() = default; |
| |
| scoped_refptr<base::SequencedTaskRunner> |
| TestPasswordStore::CreateBackgroundTaskRunner() const { |
| return base::SequencedTaskRunnerHandle::Get(); |
| } |
| |
| void TestPasswordStore::InitBackend( |
| base::RepeatingClosure sync_enabled_or_disabled_cb, |
| base::OnceCallback<void(bool)> completion) { |
| main_task_runner()->PostTask(FROM_HERE, |
| base::BindOnce(std::move(completion), true)); |
| } |
| |
| void TestPasswordStore::GetAllLoginsAsync(LoginsReply callback) { |
| background_task_runner()->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&TestPasswordStore::GetAllLoginsInternal, |
| RetainedRef(this)), |
| std::move(callback)); |
| } |
| |
| void TestPasswordStore::GetAutofillableLoginsAsync(LoginsReply callback) { |
| background_task_runner()->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&TestPasswordStore::GetAutofillableLoginsInternal, |
| RetainedRef(this)), |
| std::move(callback)); |
| } |
| |
| void TestPasswordStore::FillMatchingLoginsAsync( |
| LoginsReply callback, |
| const std::vector<PasswordFormDigest>& forms) { |
| background_task_runner()->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&TestPasswordStore::FillMatchingLoginsBulk, |
| base::Unretained(this), forms), |
| std::move(callback)); |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::AddLoginImpl( |
| const PasswordForm& form, |
| AddLoginError* error) { |
| if (error) |
| *error = AddLoginError::kNone; |
| |
| PasswordStoreChangeList changes; |
| auto& passwords_for_signon_realm = stored_passwords_[form.signon_realm]; |
| auto iter = std::find_if( |
| passwords_for_signon_realm.begin(), passwords_for_signon_realm.end(), |
| [&form](const auto& password) { |
| return ArePasswordFormUniqueKeysEqual(form, password); |
| }); |
| |
| if (iter != passwords_for_signon_realm.end()) { |
| changes.emplace_back(PasswordStoreChange::REMOVE, *iter); |
| changes.emplace_back(PasswordStoreChange::ADD, form); |
| *iter = form; |
| iter->in_store = IsAccountStore() ? PasswordForm::Store::kAccountStore |
| : PasswordForm::Store::kProfileStore; |
| return changes; |
| } |
| |
| changes.emplace_back(PasswordStoreChange::ADD, form); |
| passwords_for_signon_realm.push_back(form); |
| passwords_for_signon_realm.back().in_store = |
| IsAccountStore() ? PasswordForm::Store::kAccountStore |
| : PasswordForm::Store::kProfileStore; |
| return changes; |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::UpdateLoginImpl( |
| const PasswordForm& form, |
| UpdateLoginError* error) { |
| if (error) |
| *error = UpdateLoginError::kNone; |
| |
| PasswordStoreChangeList changes; |
| std::vector<PasswordForm>& forms = stored_passwords_[form.signon_realm]; |
| for (auto& stored_form : forms) { |
| if (ArePasswordFormUniqueKeysEqual(form, stored_form)) { |
| stored_form = form; |
| stored_form.in_store = IsAccountStore() |
| ? PasswordForm::Store::kAccountStore |
| : PasswordForm::Store::kProfileStore; |
| changes.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form)); |
| } |
| } |
| return changes; |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::RemoveLoginImpl( |
| const PasswordForm& form) { |
| PasswordStoreChangeList changes; |
| std::vector<PasswordForm>& forms = stored_passwords_[form.signon_realm]; |
| auto it = forms.begin(); |
| while (it != forms.end()) { |
| if (ArePasswordFormUniqueKeysEqual(form, *it)) { |
| it = forms.erase(it); |
| changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form)); |
| } else { |
| ++it; |
| } |
| } |
| return changes; |
| } |
| |
| std::vector<std::unique_ptr<PasswordForm>> |
| TestPasswordStore::FillMatchingLogins(const PasswordFormDigest& form) { |
| ++fill_matching_logins_calls_; |
| std::vector<std::unique_ptr<PasswordForm>> matched_forms; |
| for (const auto& elements : stored_passwords_) { |
| // The code below doesn't support PSL federated credential. It's doable but |
| // no tests need it so far. |
| const bool realm_matches = elements.first == form.signon_realm; |
| const bool realm_psl_matches = |
| IsPublicSuffixDomainMatch(elements.first, form.signon_realm); |
| if (realm_matches || realm_psl_matches || |
| (form.scheme == PasswordForm::Scheme::kHtml && |
| password_manager::IsFederatedRealm(elements.first, form.url))) { |
| const bool is_psl = !realm_matches && realm_psl_matches; |
| for (const auto& stored_form : elements.second) { |
| // Repeat the condition above with an additional check for origin. |
| if (realm_matches || realm_psl_matches || |
| (form.scheme == PasswordForm::Scheme::kHtml && |
| stored_form.url.GetOrigin() == form.url.GetOrigin() && |
| password_manager::IsFederatedRealm(stored_form.signon_realm, |
| form.url))) { |
| matched_forms.push_back(std::make_unique<PasswordForm>(stored_form)); |
| matched_forms.back()->is_public_suffix_match = is_psl; |
| } |
| } |
| } |
| } |
| return matched_forms; |
| } |
| |
| std::vector<std::unique_ptr<PasswordForm>> |
| TestPasswordStore::FillMatchingLoginsByPassword( |
| const std::u16string& plain_text_password) { |
| std::vector<std::unique_ptr<PasswordForm>> matched_forms; |
| for (const auto& elements : stored_passwords_) { |
| for (const auto& password_form : elements.second) { |
| if (password_form.password_value == plain_text_password) |
| matched_forms.push_back(std::make_unique<PasswordForm>(password_form)); |
| } |
| } |
| return matched_forms; |
| } |
| |
| DatabaseCleanupResult TestPasswordStore::DeleteUndecryptableLogins() { |
| return DatabaseCleanupResult::kSuccess; |
| } |
| |
| std::vector<InteractionsStats> TestPasswordStore::GetSiteStatsImpl( |
| const GURL& origin_domain) { |
| return std::vector<InteractionsStats>(); |
| } |
| |
| void TestPasswordStore::ReportMetricsImpl(const std::string& sync_username, |
| bool custom_passphrase_sync_enabled, |
| BulkCheckDone bulk_check_done) { |
| NOTIMPLEMENTED(); |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::RemoveLoginsByURLAndTimeImpl( |
| const base::RepeatingCallback<bool(const GURL&)>& url_filter, |
| base::Time begin, |
| base::Time end) { |
| NOTIMPLEMENTED(); |
| return PasswordStoreChangeList(); |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::RemoveLoginsCreatedBetweenImpl( |
| base::Time begin, |
| base::Time end) { |
| NOTIMPLEMENTED(); |
| return PasswordStoreChangeList(); |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::DisableAutoSignInForOriginsImpl( |
| const base::RepeatingCallback<bool(const GURL&)>& origin_filter) { |
| NOTIMPLEMENTED(); |
| return PasswordStoreChangeList(); |
| } |
| |
| bool TestPasswordStore::RemoveStatisticsByOriginAndTimeImpl( |
| const base::RepeatingCallback<bool(const GURL&)>& origin_filter, |
| base::Time delete_begin, |
| base::Time delete_end) { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| void TestPasswordStore::AddSiteStatsImpl(const InteractionsStats& stats) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void TestPasswordStore::RemoveSiteStatsImpl(const GURL& origin_domain) { |
| NOTIMPLEMENTED(); |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::AddInsecureCredentialImpl( |
| const InsecureCredential& insecure_credential) { |
| InsecureCredential cred = insecure_credential; |
| cred.in_store = IsAccountStore() ? PasswordForm::Store::kAccountStore |
| : PasswordForm::Store::kProfileStore; |
| if (!insecure_credentials_.insert(std::move(cred)).second) |
| return {}; |
| |
| PasswordStoreChangeList changes; |
| for (auto& form : stored_passwords_[insecure_credential.signon_realm]) { |
| if (form.username_value == insecure_credential.username) { |
| form.password_issues->insert( |
| {insecure_credential.insecure_type, |
| InsecurityMetadata(insecure_credential.create_time, |
| insecure_credential.is_muted)}); |
| changes.emplace_back(PasswordStoreChange::UPDATE, form); |
| } |
| } |
| return changes; |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::RemoveInsecureCredentialsImpl( |
| const std::string& signon_realm, |
| const std::u16string& username, |
| RemoveInsecureCredentialsReason reason) { |
| const size_t old_size = insecure_credentials_.size(); |
| base::EraseIf(insecure_credentials_, [&](const auto& credential) { |
| return credential.signon_realm == signon_realm && |
| credential.username == username; |
| }); |
| |
| if (old_size == insecure_credentials_.size()) |
| return {}; |
| |
| PasswordStoreChangeList changes; |
| for (auto& form : stored_passwords_[signon_realm]) { |
| if (form.username_value == username) { |
| form.password_issues->clear(); |
| changes.emplace_back(PasswordStoreChange::UPDATE, form); |
| } |
| } |
| return changes; |
| } |
| |
| std::vector<InsecureCredential> |
| TestPasswordStore::GetAllInsecureCredentialsImpl() { |
| return std::vector<InsecureCredential>(insecure_credentials_.begin(), |
| insecure_credentials_.end()); |
| } |
| |
| std::vector<InsecureCredential> |
| TestPasswordStore::GetMatchingInsecureCredentialsImpl( |
| const std::string& signon_realm) { |
| std::vector<InsecureCredential> result; |
| std::copy_if(insecure_credentials_.begin(), insecure_credentials_.end(), |
| std::back_inserter(result), |
| [&signon_realm](const InsecureCredential& credential) { |
| return credential.signon_realm == signon_realm; |
| }); |
| return result; |
| } |
| |
| void TestPasswordStore::AddFieldInfoImpl(const FieldInfo& field_info) { |
| NOTIMPLEMENTED(); |
| } |
| std::vector<FieldInfo> TestPasswordStore::GetAllFieldInfoImpl() { |
| NOTIMPLEMENTED(); |
| return std::vector<FieldInfo>(); |
| } |
| |
| void TestPasswordStore::RemoveFieldInfoByTimeImpl(base::Time remove_begin, |
| base::Time remove_end) { |
| NOTIMPLEMENTED(); |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::AddLoginSync( |
| const PasswordForm& form, |
| AddLoginError* error) { |
| NOTIMPLEMENTED(); |
| return {}; |
| } |
| |
| bool TestPasswordStore::AddInsecureCredentialsSync( |
| base::span<const InsecureCredential> credentials) { |
| NOTIMPLEMENTED(); |
| return true; |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::UpdateLoginSync( |
| const PasswordForm& form, |
| UpdateLoginError* error) { |
| NOTIMPLEMENTED(); |
| return {}; |
| } |
| |
| bool TestPasswordStore::UpdateInsecureCredentialsSync( |
| const PasswordForm& form, |
| base::span<const InsecureCredential> credentials) { |
| NOTIMPLEMENTED(); |
| return true; |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::RemoveLoginSync( |
| const PasswordForm& form) { |
| NOTIMPLEMENTED(); |
| return {}; |
| } |
| |
| bool TestPasswordStore::BeginTransaction() { |
| return true; |
| } |
| |
| void TestPasswordStore::RollbackTransaction() { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool TestPasswordStore::CommitTransaction() { |
| return true; |
| } |
| |
| FormRetrievalResult TestPasswordStore::ReadAllLogins( |
| PrimaryKeyToFormMap* key_to_form_map) { |
| if (stored_passwords_.empty()) { |
| key_to_form_map->clear(); |
| return FormRetrievalResult::kSuccess; |
| } |
| // This currently can't be implemented properly, since TestPasswordStore |
| // doesn't have primary keys. Right now no tests actually depend on it, so |
| // just leave it not implemented. |
| NOTIMPLEMENTED(); |
| return FormRetrievalResult::kDbError; |
| } |
| |
| std::vector<InsecureCredential> TestPasswordStore::ReadSecurityIssues( |
| FormPrimaryKey parent_key) { |
| NOTIMPLEMENTED(); |
| return std::vector<InsecureCredential>(); |
| } |
| |
| PasswordStoreChangeList TestPasswordStore::RemoveLoginByPrimaryKeySync( |
| FormPrimaryKey primary_key) { |
| NOTIMPLEMENTED(); |
| return PasswordStoreChangeList(); |
| } |
| |
| PasswordStoreSync::MetadataStore* TestPasswordStore::GetMetadataStore() { |
| return metadata_store_.get(); |
| } |
| |
| bool TestPasswordStore::IsAccountStore() const { |
| return is_account_store_.value(); |
| } |
| |
| bool TestPasswordStore::DeleteAndRecreateDatabaseFile() { |
| stored_passwords_.clear(); |
| metadata_store_->DeleteAllSyncMetadata(); |
| return true; |
| } |
| |
| LoginsResult TestPasswordStore::GetAllLoginsInternal() { |
| LoginsResult forms; |
| for (const auto& elements : stored_passwords_) { |
| for (const auto& password_form : elements.second) { |
| forms.push_back(std::make_unique<PasswordForm>(password_form)); |
| } |
| } |
| return forms; |
| } |
| |
| LoginsResult TestPasswordStore::GetAutofillableLoginsInternal() { |
| LoginsResult forms; |
| for (const auto& forms_for_realm : stored_passwords_) { |
| for (const PasswordForm& form : forms_for_realm.second) { |
| if (!form.blocked_by_user) |
| forms.push_back(std::make_unique<PasswordForm>(form)); |
| } |
| } |
| return forms; |
| } |
| |
| LoginsResult TestPasswordStore::FillMatchingLoginsBulk( |
| const std::vector<PasswordFormDigest>& forms) { |
| std::vector<std::unique_ptr<PasswordForm>> results; |
| for (const auto& form : forms) { |
| std::vector<std::unique_ptr<PasswordForm>> matched_forms = |
| FillMatchingLogins(form); |
| results.insert(results.end(), |
| std::make_move_iterator(matched_forms.begin()), |
| std::make_move_iterator(matched_forms.end())); |
| } |
| return results; |
| } |
| |
| } // namespace password_manager |