diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/password_manager | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/password_manager')
209 files changed, 5451 insertions, 2704 deletions
diff --git a/chromium/components/password_manager/content/browser/bad_message.cc b/chromium/components/password_manager/content/browser/bad_message.cc index 764e9333468..73b6e360b4c 100644 --- a/chromium/components/password_manager/content/browser/bad_message.cc +++ b/chromium/components/password_manager/content/browser/bad_message.cc @@ -59,7 +59,7 @@ bool CheckChildProcessSecurityPolicy( content::RenderFrameHost* frame, const autofill::PasswordForm& password_form, BadMessageReason reason) { - return CheckChildProcessSecurityPolicyForURL(frame, password_form.origin, + return CheckChildProcessSecurityPolicyForURL(frame, password_form.url, reason) && CheckChildProcessSecurityPolicyForURL( frame, GURL(password_form.signon_realm), reason) && diff --git a/chromium/components/password_manager/content/browser/content_credential_manager.cc b/chromium/components/password_manager/content/browser/content_credential_manager.cc index d857c92b044..67e3569a931 100644 --- a/chromium/components/password_manager/content/browser/content_credential_manager.cc +++ b/chromium/components/password_manager/content/browser/content_credential_manager.cc @@ -16,7 +16,7 @@ ContentCredentialManager::ContentCredentialManager( PasswordManagerClient* client) : impl_(client) {} -ContentCredentialManager::~ContentCredentialManager() {} +ContentCredentialManager::~ContentCredentialManager() = default; void ContentCredentialManager::BindRequest( mojo::PendingReceiver<blink::mojom::CredentialManager> receiver) { diff --git a/chromium/components/password_manager/content/browser/content_password_manager_driver.cc b/chromium/components/password_manager/content/browser/content_password_manager_driver.cc index 108a095999e..cfda4606ff5 100644 --- a/chromium/components/password_manager/content/browser/content_password_manager_driver.cc +++ b/chromium/components/password_manager/content/browser/content_password_manager_driver.cc @@ -111,9 +111,11 @@ void ContentPasswordManagerDriver::FillPasswordForm( autofill::MaybeClearPasswordValues(form_data)); } -void ContentPasswordManagerDriver::InformNoSavedCredentials() { +void ContentPasswordManagerDriver::InformNoSavedCredentials( + bool should_show_popup_without_passwords) { GetPasswordAutofillManager()->OnNoCredentialsFound(); - GetPasswordAutofillAgent()->InformNoSavedCredentials(); + GetPasswordAutofillAgent()->InformNoSavedCredentials( + should_show_popup_without_passwords); } void ContentPasswordManagerDriver::FormEligibleForGenerationFound( @@ -256,10 +258,11 @@ void ContentPasswordManagerDriver::ShowManualFallbackForSaving( GetPasswordManager()->ShowManualFallbackForSaving(this, form_data); if (client_->IsIsolationForPasswordSitesEnabled()) { - // This function signals that the user is typing a password into - // password form. Use this as a heuristic to start site-isolating the - // form's site. This is intended to be used primarily when full site - // isolation is not used, such as on Android. + // This function signals that a password field has been filled (whether by + // the user, JS, autofill, or some other means) or a password form has been + // submitted. Use this as a heuristic to start site-isolating the form's + // site. This is intended to be used primarily when full site isolation is + // not used, such as on Android. content::SiteInstance::StartIsolatingSite( render_frame_host_->GetSiteInstance()->GetBrowserContext(), form_data.url); diff --git a/chromium/components/password_manager/content/browser/content_password_manager_driver.h b/chromium/components/password_manager/content/browser/content_password_manager_driver.h index de351accb5e..5aa51d0f3a7 100644 --- a/chromium/components/password_manager/content/browser/content_password_manager_driver.h +++ b/chromium/components/password_manager/content/browser/content_password_manager_driver.h @@ -53,7 +53,8 @@ class ContentPasswordManagerDriver int GetId() const override; void FillPasswordForm( const autofill::PasswordFormFillData& form_data) override; - void InformNoSavedCredentials() override; + void InformNoSavedCredentials( + bool should_show_popup_without_passwords) override; void FormEligibleForGenerationFound( const autofill::PasswordFormGenerationData& form) override; void GeneratedPasswordAccepted(const base::string16& password) override; diff --git a/chromium/components/password_manager/content/browser/content_password_manager_driver_factory.cc b/chromium/components/password_manager/content/browser/content_password_manager_driver_factory.cc index 1a595a784c6..1d309b25f3a 100644 --- a/chromium/components/password_manager/content/browser/content_password_manager_driver_factory.cc +++ b/chromium/components/password_manager/content/browser/content_password_manager_driver_factory.cc @@ -56,7 +56,8 @@ ContentPasswordManagerDriverFactory::ContentPasswordManagerDriverFactory( password_client_(password_client), autofill_client_(autofill_client) {} -ContentPasswordManagerDriverFactory::~ContentPasswordManagerDriverFactory() {} +ContentPasswordManagerDriverFactory::~ContentPasswordManagerDriverFactory() = + default; // static ContentPasswordManagerDriverFactory* diff --git a/chromium/components/password_manager/content/browser/content_password_manager_driver_unittest.cc b/chromium/components/password_manager/content/browser/content_password_manager_driver_unittest.cc index d89a7483d02..66ca88b17ce 100644 --- a/chromium/components/password_manager/content/browser/content_password_manager_driver_unittest.cc +++ b/chromium/components/password_manager/content/browser/content_password_manager_driver_unittest.cc @@ -61,13 +61,6 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { class FakePasswordAutofillAgent : public autofill::mojom::PasswordAutofillAgent { public: - FakePasswordAutofillAgent() - : called_set_logging_state_(false), - logging_state_active_(false), - receiver_(this) {} - - ~FakePasswordAutofillAgent() override {} - void BindPendingReceiver(mojo::ScopedInterfaceEndpointHandle handle) { receiver_.Bind( mojo::PendingAssociatedReceiver<autofill::mojom::PasswordAutofillAgent>( @@ -85,7 +78,7 @@ class FakePasswordAutofillAgent // autofill::mojom::PasswordAutofillAgent: MOCK_METHOD1(FillPasswordForm, void(const PasswordFormFillData&)); - MOCK_METHOD0(InformNoSavedCredentials, void()); + MOCK_METHOD1(InformNoSavedCredentials, void(bool)); MOCK_METHOD2(FillIntoFocusedField, void(bool, const base::string16&)); MOCK_METHOD1(TouchToFillClosed, void(bool)); MOCK_METHOD1(AnnotateFieldsWithParsingResult, void(const ParsingResult&)); @@ -99,17 +92,18 @@ class FakePasswordAutofillAgent } // Records whether SetLoggingState() gets called. - bool called_set_logging_state_; + bool called_set_logging_state_ = false; // Records data received via SetLoggingState() call. - bool logging_state_active_; + bool logging_state_active_ = false; - mojo::AssociatedReceiver<autofill::mojom::PasswordAutofillAgent> receiver_; + mojo::AssociatedReceiver<autofill::mojom::PasswordAutofillAgent> receiver_{ + this}; }; PasswordFormFillData GetTestPasswordFormFillData() { // Create the current form on the page. PasswordForm form_on_page; - form_on_page.origin = GURL("https://foo.com/"); + form_on_page.url = GURL("https://foo.com/"); form_on_page.action = GURL("https://foo.com/login"); form_on_page.signon_realm = "https://foo.com/"; form_on_page.scheme = PasswordForm::Scheme::kHtml; diff --git a/chromium/components/password_manager/content/browser/password_manager_log_router_factory_unittest.cc b/chromium/components/password_manager/content/browser/password_manager_log_router_factory_unittest.cc index eae9faafab7..9e407ecc252 100644 --- a/chromium/components/password_manager/content/browser/password_manager_log_router_factory_unittest.cc +++ b/chromium/components/password_manager/content/browser/password_manager_log_router_factory_unittest.cc @@ -21,8 +21,6 @@ const char kTestText[] = "abcd1234"; class MockLogReceiver : public autofill::LogReceiver { public: - MockLogReceiver() {} - MOCK_METHOD1(LogEntry, void(const base::Value&)); }; diff --git a/chromium/components/password_manager/content/browser/password_requirements_service_factory.cc b/chromium/components/password_manager/content/browser/password_requirements_service_factory.cc index 07f4b0665a7..72f194508e0 100644 --- a/chromium/components/password_manager/content/browser/password_requirements_service_factory.cc +++ b/chromium/components/password_manager/content/browser/password_requirements_service_factory.cc @@ -36,7 +36,8 @@ PasswordRequirementsServiceFactory::PasswordRequirementsServiceFactory() "PasswordRequirementsServiceFactory", BrowserContextDependencyManager::GetInstance()) {} -PasswordRequirementsServiceFactory::~PasswordRequirementsServiceFactory() {} +PasswordRequirementsServiceFactory::~PasswordRequirementsServiceFactory() = + default; KeyedService* PasswordRequirementsServiceFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { diff --git a/chromium/components/password_manager/core/browser/BUILD.gn b/chromium/components/password_manager/core/browser/BUILD.gn index 6732d2b25b2..f642161af5f 100644 --- a/chromium/components/password_manager/core/browser/BUILD.gn +++ b/chromium/components/password_manager/core/browser/BUILD.gn @@ -58,6 +58,8 @@ jumbo_static_library("browser") { "browser_save_password_progress_logger.h", "bulk_leak_check_service.cc", "bulk_leak_check_service.h", + "bulk_leak_check_service_interface.cc", + "bulk_leak_check_service_interface.h", "compromised_credentials_consumer.cc", "compromised_credentials_consumer.h", "compromised_credentials_observer.cc", @@ -216,8 +218,8 @@ jumbo_static_library("browser") { "sync_credentials_filter.h", "ui/bulk_leak_check_service_adapter.cc", "ui/bulk_leak_check_service_adapter.h", - "ui/compromised_credentials_provider.cc", - "ui/compromised_credentials_provider.h", + "ui/compromised_credentials_manager.cc", + "ui/compromised_credentials_manager.h", "ui/credential_provider_interface.h", "ui/credential_utils.h", "ui/export_flow.h", @@ -226,6 +228,8 @@ jumbo_static_library("browser") { "ui/password_check_referrer.cc", "ui/password_check_referrer.h", "ui/plaintext_reason.h", + "ui/post_save_compromised_helper.cc", + "ui/post_save_compromised_helper.h", "ui/saved_passwords_presenter.cc", "ui/saved_passwords_presenter.h", "votes_uploader.cc", @@ -261,6 +265,7 @@ jumbo_static_library("browser") { ":proto", "//base", "//base:i18n", + "//base/util/ranges:ranges", "//components/autofill/core/browser", "//components/autofill/core/browser/proto", "//components/autofill/core/common", @@ -427,6 +432,8 @@ jumbo_static_library("test_support") { "android_affiliation/mock_affiliation_consumer.h", "fake_form_fetcher.cc", "fake_form_fetcher.h", + "mock_bulk_leak_check_service.cc", + "mock_bulk_leak_check_service.h", "mock_password_feature_manager.cc", "mock_password_feature_manager.h", "mock_password_form_manager_for_ui.cc", @@ -451,11 +458,13 @@ jumbo_static_library("test_support") { ":browser", ":hash_password_manager", "//components/autofill/core/browser:test_support", + "//components/keyed_service/core", + "//components/password_manager/core/browser/leak_detection", "//components/safe_browsing:buildflags", "//components/ukm", "//services/network/public/cpp", "//testing/gmock", - "//url:url", + "//url", ] deps = [ ":affiliation", @@ -584,7 +593,8 @@ source_set("unit_tests") { "sync_username_test_base.cc", "sync_username_test_base.h", "ui/bulk_leak_check_service_adapter_unittest.cc", - "ui/compromised_credentials_provider_unittest.cc", + "ui/compromised_credentials_manager_unittest.cc", + "ui/post_save_compromised_helper_unittest.cc", "ui/saved_passwords_presenter_unittest.cc", "vote_uploads_test_matchers.h", "votes_uploader_unittest.cc", diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliated_match_helper_unittest.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliated_match_helper_unittest.cc index 03e2b1d891b..27a948266b3 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliated_match_helper_unittest.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliated_match_helper_unittest.cc @@ -12,6 +12,7 @@ #include "base/bind.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/test/scoped_mock_time_message_loop_task_runner.h" @@ -34,8 +35,6 @@ class MockAffiliationService : public testing::StrictMock<AffiliationService> { testing::DefaultValue<AffiliatedFacets>::Set(AffiliatedFacets()); } - ~MockAffiliationService() override {} - MOCK_METHOD2(OnGetAffiliationsAndBrandingCalled, AffiliatedFacets(const FacetURI&, StrategyOnCacheMiss)); MOCK_METHOD2(Prefetch, void(const FacetURI&, const base::Time&)); @@ -173,9 +172,7 @@ PasswordStore::FormDigest GetTestObservedWebForm(const char* signon_realm, class AffiliatedMatchHelperTest : public testing::Test { public: - AffiliatedMatchHelperTest() - : expecting_result_callback_(false), mock_affiliation_service_(nullptr) {} - ~AffiliatedMatchHelperTest() override {} + AffiliatedMatchHelperTest() = default; protected: void RunDeferredInitialization() { @@ -329,15 +326,13 @@ class AffiliatedMatchHelperTest : public testing::Test { // testing::Test: void SetUp() override { - std::unique_ptr<MockAffiliationService> service( - new MockAffiliationService()); + auto service = std::make_unique<MockAffiliationService>(); mock_affiliation_service_ = service.get(); - password_store_ = new TestPasswordStore; password_store_->Init(nullptr); - match_helper_.reset( - new AffiliatedMatchHelper(password_store_.get(), std::move(service))); + match_helper_ = std::make_unique<AffiliatedMatchHelper>( + password_store_.get(), std::move(service)); } void TearDown() override { @@ -353,13 +348,14 @@ class AffiliatedMatchHelperTest : public testing::Test { std::vector<std::string> last_result_realms_; std::vector<std::unique_ptr<autofill::PasswordForm>> last_result_forms_; - bool expecting_result_callback_; + bool expecting_result_callback_ = false; - scoped_refptr<TestPasswordStore> password_store_; + scoped_refptr<TestPasswordStore> password_store_ = + base::MakeRefCounted<TestPasswordStore>(); std::unique_ptr<AffiliatedMatchHelper> match_helper_; // Owned by |match_helper_|. - MockAffiliationService* mock_affiliation_service_; + MockAffiliationService* mock_affiliation_service_ = nullptr; DISALLOW_COPY_AND_ASSIGN(AffiliatedMatchHelperTest); }; @@ -505,7 +501,7 @@ TEST_F(AffiliatedMatchHelperTest, InjectAffiliationAndBrandingInformation) { autofill::PasswordForm web_form; web_form.scheme = digest.scheme; web_form.signon_realm = digest.signon_realm; - web_form.origin = digest.origin; + web_form.url = digest.url; forms.push_back(std::make_unique<autofill::PasswordForm>(web_form)); size_t expected_form_count = forms.size(); diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc index 7f785e8659e..dea78886d63 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend.cc @@ -37,8 +37,7 @@ AffiliationBackend::AffiliationBackend( DETACH_FROM_SEQUENCE(sequence_checker_); } -AffiliationBackend::~AffiliationBackend() { -} +AffiliationBackend::~AffiliationBackend() = default; void AffiliationBackend::Initialize( std::unique_ptr<network::PendingSharedURLLoaderFactory> @@ -47,14 +46,14 @@ void AffiliationBackend::Initialize( const base::FilePath& db_path) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!throttler_); - throttler_.reset(new AffiliationFetchThrottler( - this, task_runner_, network_connection_tracker, tick_clock_)); + throttler_ = std::make_unique<AffiliationFetchThrottler>( + this, task_runner_, network_connection_tracker, tick_clock_); // TODO(engedy): Currently, when Init() returns false, it always poisons the // DB, so subsequent operations will silently fail. Consider either fully // committing to this approach and making Init() a void, or handling the // return value here. See: https://crbug.com/478831. - cache_.reset(new AffiliationDatabase()); + cache_ = std::make_unique<AffiliationDatabase>(); cache_->Init(db_path); DCHECK(pending_url_loader_factory); DCHECK(!url_loader_factory_); diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend_unittest.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend_unittest.cc index 339bf8f8540..c3a264a5e6f 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend_unittest.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_backend_unittest.cc @@ -12,6 +12,7 @@ #include "base/files/file_util.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" #include "base/test/task_environment.h" #include "base/test/test_mock_time_task_runner.h" #include "base/test/test_simple_task_runner.h" @@ -160,11 +161,7 @@ base::TimeDelta Epsilon() { class AffiliationBackendTest : public testing::Test { public: - AffiliationBackendTest() - : backend_task_runner_(new base::TestMockTimeTaskRunner), - consumer_task_runner_(new base::TestSimpleTaskRunner), - mock_fetch_throttler_(nullptr) {} - ~AffiliationBackendTest() override {} + AffiliationBackendTest() = default; protected: void GetAffiliationsAndBranding(MockAffiliationConsumer* consumer, @@ -341,9 +338,9 @@ class AffiliationBackendTest : public testing::Test { // testing::Test: void SetUp() override { ASSERT_TRUE(CreateTemporaryFile(&db_path_)); - backend_.reset(new AffiliationBackend( + backend_ = std::make_unique<AffiliationBackend>( backend_task_runner_, backend_task_runner_->GetMockClock(), - backend_task_runner_->GetMockTickClock())); + backend_task_runner_->GetMockTickClock()); auto test_shared_loader_factory = base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( &test_url_loader_factory_); @@ -364,15 +361,18 @@ class AffiliationBackendTest : public testing::Test { } base::test::TaskEnvironment task_environment_; - scoped_refptr<base::TestMockTimeTaskRunner> backend_task_runner_; - scoped_refptr<base::TestSimpleTaskRunner> consumer_task_runner_; + scoped_refptr<base::TestMockTimeTaskRunner> backend_task_runner_ = + base::MakeRefCounted<base::TestMockTimeTaskRunner>(); + scoped_refptr<base::TestSimpleTaskRunner> consumer_task_runner_ = + base::MakeRefCounted<base::TestSimpleTaskRunner>(); base::FilePath db_path_; ScopedFakeAffiliationAPI fake_affiliation_api_; MockAffiliationConsumer mock_consumer_; std::unique_ptr<AffiliationBackend> backend_; network::TestURLLoaderFactory test_url_loader_factory_; - MockAffiliationFetchThrottler* mock_fetch_throttler_; // Owned by |backend_|. + // Owned by |backend_|. + MockAffiliationFetchThrottler* mock_fetch_throttler_ = nullptr; DISALLOW_COPY_AND_ASSIGN(AffiliationBackendTest); }; diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database.cc index b08bbdf3794..30ae8a92936 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database.cc @@ -6,6 +6,8 @@ #include <stdint.h> +#include <memory> + #include <vector> #include "base/bind.h" @@ -30,14 +32,12 @@ const int kCompatibleVersion = 1; } // namespace -AffiliationDatabase::AffiliationDatabase() { -} +AffiliationDatabase::AffiliationDatabase() = default; -AffiliationDatabase::~AffiliationDatabase() { -} +AffiliationDatabase::~AffiliationDatabase() = default; bool AffiliationDatabase::Init(const base::FilePath& path) { - sql_connection_.reset(new sql::Database); + sql_connection_ = std::make_unique<sql::Database>(); sql_connection_->set_histogram_tag("Affiliation"); sql_connection_->set_error_callback(base::BindRepeating( &AffiliationDatabase::SQLErrorCallback, base::Unretained(this))); diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database_unittest.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database_unittest.cc index 56d311b6a53..724756ce390 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database_unittest.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_database_unittest.cc @@ -6,8 +6,11 @@ #include <stdint.h> +#include <memory> + #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" +#include "base/logging.h" #include "base/macros.h" #include "base/path_service.h" #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h" @@ -80,8 +83,7 @@ AffiliatedFacetsWithUpdateTime TestEquivalenceClass3() { class AffiliationDatabaseTest : public testing::Test { public: - AffiliationDatabaseTest() {} - ~AffiliationDatabaseTest() override {} + AffiliationDatabaseTest() = default; void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); @@ -89,7 +91,7 @@ class AffiliationDatabaseTest : public testing::Test { } void OpenDatabase() { - db_.reset(new AffiliationDatabase); + db_ = std::make_unique<AffiliationDatabase>(); ASSERT_TRUE(db_->Init(db_path())); } diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetch_throttler_unittest.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetch_throttler_unittest.cc index 8dba0afeca3..a931dbc7a0b 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetch_throttler_unittest.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetch_throttler_unittest.cc @@ -13,6 +13,7 @@ #include "base/callback.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" #include "base/numerics/safe_math.h" #include "base/test/task_environment.h" #include "base/test/test_mock_time_task_runner.h" @@ -63,13 +64,7 @@ class MockAffiliationFetchThrottlerDelegate class AffiliationFetchThrottlerTest : public testing::Test { public: - AffiliationFetchThrottlerTest() - : task_runner_(new base::TestMockTimeTaskRunner), - mock_delegate_(task_runner_->GetMockTickClock()) { - SimulateHasNetworkConnectivity(true); - } - - ~AffiliationFetchThrottlerTest() override {} + AffiliationFetchThrottlerTest() { SimulateHasNetworkConnectivity(true); } std::unique_ptr<AffiliationFetchThrottler> CreateThrottler() { return std::make_unique<AffiliationFetchThrottler>( @@ -128,8 +123,10 @@ class AffiliationFetchThrottlerTest : public testing::Test { // Needed because NetworkConnectionTracker uses base::ObserverList, which // notifies observers on the sequence from which they have registered. base::test::TaskEnvironment task_environment_; - scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; - MockAffiliationFetchThrottlerDelegate mock_delegate_; + scoped_refptr<base::TestMockTimeTaskRunner> task_runner_ = + base::MakeRefCounted<base::TestMockTimeTaskRunner>(); + MockAffiliationFetchThrottlerDelegate mock_delegate_{ + task_runner_->GetMockTickClock()}; DISALLOW_COPY_AND_ASSIGN(AffiliationFetchThrottlerTest); }; diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc index 95701792649..b3bbda151e9 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_fetcher_unittest.cc @@ -35,8 +35,7 @@ const char kExampleWebFacet2URI[] = "https://www.example.org"; class MockAffiliationFetcherDelegate : public testing::StrictMock<AffiliationFetcherDelegate> { public: - MockAffiliationFetcherDelegate() {} - ~MockAffiliationFetcherDelegate() {} + MockAffiliationFetcherDelegate() = default; MOCK_METHOD0(OnFetchSucceededProxy, void()); MOCK_METHOD0(OnFetchFailed, void()); @@ -59,10 +58,7 @@ class MockAffiliationFetcherDelegate class AffiliationFetcherTest : public testing::Test { public: - AffiliationFetcherTest() - : test_shared_loader_factory_( - base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( - &test_url_loader_factory_)) { + AffiliationFetcherTest() { test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting( [&](const network::ResourceRequest& request) { intercepted_body_ = network::GetUploadData(request); @@ -70,8 +66,6 @@ class AffiliationFetcherTest : public testing::Test { })); } - ~AffiliationFetcherTest() override {} - protected: void VerifyRequestPayload(const std::vector<FacetURI>& expected_facet_uris) { affiliation_pb::LookupAffiliationRequest request; @@ -121,7 +115,9 @@ class AffiliationFetcherTest : public testing::Test { private: base::test::TaskEnvironment task_environment_; network::TestURLLoaderFactory test_url_loader_factory_; - scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_; + scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_ = + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory_); std::string intercepted_body_; net::HttpRequestHeaders intercepted_headers_; diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_service_unittest.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_service_unittest.cc index 36c337b3145..686f51f8c0a 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_service_unittest.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_service_unittest.cc @@ -13,6 +13,7 @@ #include "base/files/file_util.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" #include "base/test/task_environment.h" #include "base/test/test_mock_time_task_runner.h" #include "base/test/test_simple_task_runner.h" @@ -49,12 +50,7 @@ AffiliatedFacets GetTestEquivalenceClassAlpha() { class AffiliationServiceTest : public testing::Test { public: - AffiliationServiceTest() - : background_task_runner_(new base::TestMockTimeTaskRunner()), - test_shared_loader_factory_( - base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( - &test_url_loader_factory_)) {} - ~AffiliationServiceTest() override {} + AffiliationServiceTest() = default; protected: void DestroyService() { service_.reset(); } @@ -81,7 +77,7 @@ class AffiliationServiceTest : public testing::Test { network::TestNetworkConnectionTracker::GetInstance(); network_connection_tracker->SetConnectionType( network::mojom::ConnectionType::CONNECTION_ETHERNET); - service_.reset(new AffiliationService(background_task_runner())); + service_ = std::make_unique<AffiliationService>(background_task_runner()); service_->Initialize(test_shared_loader_factory_, network_connection_tracker, database_path); // Note: the background task runner is purposely not pumped here, so that @@ -98,9 +94,12 @@ class AffiliationServiceTest : public testing::Test { background_task_runner_->RunUntilIdle(); } - scoped_refptr<base::TestMockTimeTaskRunner> background_task_runner_; + scoped_refptr<base::TestMockTimeTaskRunner> background_task_runner_ = + base::MakeRefCounted<base::TestMockTimeTaskRunner>(); network::TestURLLoaderFactory test_url_loader_factory_; - scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_; + scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_ = + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory_); ScopedFakeAffiliationAPI fake_affiliation_api_; MockAffiliationConsumer mock_consumer_; diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc index 74904c302f5..a073adb0bc6 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.cc @@ -268,15 +268,12 @@ FacetURI::FacetURI(const std::string& canonical_spec, bool is_valid) // AffiliatedFacetsWithUpdateTime --------------------------------------------- -AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime() { -} +AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime() = default; AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime( const AffiliatedFacetsWithUpdateTime& other) = default; -AffiliatedFacetsWithUpdateTime::~AffiliatedFacetsWithUpdateTime() { -} - +AffiliatedFacetsWithUpdateTime::~AffiliatedFacetsWithUpdateTime() = default; // Helpers -------------------------------------------------------------------- diff --git a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.h b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.h index 65a3ee0f2eb..cebec0a3215 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.h +++ b/chromium/components/password_manager/core/browser/android_affiliation/affiliation_utils.h @@ -49,7 +49,7 @@ #include <string> #include <vector> -#include "base/logging.h" +#include "base/check.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "url/gurl.h" diff --git a/chromium/components/password_manager/core/browser/android_affiliation/facet_manager_unittest.cc b/chromium/components/password_manager/core/browser/android_affiliation/facet_manager_unittest.cc index a6b7bd9c68e..9e9bd6afcdd 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/facet_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/facet_manager_unittest.cc @@ -83,9 +83,7 @@ class TestFacetManagerNotifier { class MockFacetManagerHost : public FacetManagerHost { public: explicit MockFacetManagerHost(TestFacetManagerNotifier* notifier) - : notifier_(notifier), signaled_need_network_request_(false) {} - - ~MockFacetManagerHost() override {} + : notifier_(notifier) {} // Sets the |facet_uri| that will be expected to appear in calls coming from // the FacetManager under test. @@ -144,7 +142,7 @@ class MockFacetManagerHost : public FacetManagerHost { FacetURI expected_facet_uri_; AffiliatedFacetsWithUpdateTime fake_database_content_; - bool signaled_need_network_request_; + bool signaled_need_network_request_ = false; DISALLOW_COPY_AND_ASSIGN(MockFacetManagerHost); }; diff --git a/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc b/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc index c926b0bb5a5..672f55f64f9 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_api.cc @@ -12,8 +12,7 @@ namespace password_manager { -ScopedFakeAffiliationAPI::ScopedFakeAffiliationAPI() { -} +ScopedFakeAffiliationAPI::ScopedFakeAffiliationAPI() = default; ScopedFakeAffiliationAPI::~ScopedFakeAffiliationAPI() { // Note that trying to provide details of dangling fetchers would be unwise, diff --git a/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc b/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc index 8fa599f63f7..c723cf173d8 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/fake_affiliation_fetcher.cc @@ -15,8 +15,7 @@ password_manager::FakeAffiliationFetcher::FakeAffiliationFetcher( AffiliationFetcherDelegate* delegate) : AffiliationFetcher(std::move(url_loader_factory), facet_ids, delegate) {} -password_manager::FakeAffiliationFetcher::~FakeAffiliationFetcher() { -} +password_manager::FakeAffiliationFetcher::~FakeAffiliationFetcher() = default; void password_manager::FakeAffiliationFetcher::SimulateSuccess( std::unique_ptr<AffiliationFetcherDelegate::Result> fake_result) { diff --git a/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliated_match_helper.cc b/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliated_match_helper.cc index d0691be56da..ec21d9d5c83 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliated_match_helper.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliated_match_helper.cc @@ -16,7 +16,7 @@ namespace password_manager { MockAffiliatedMatchHelper::MockAffiliatedMatchHelper() : AffiliatedMatchHelper(nullptr, nullptr) {} -MockAffiliatedMatchHelper::~MockAffiliatedMatchHelper() {} +MockAffiliatedMatchHelper::~MockAffiliatedMatchHelper() = default; void MockAffiliatedMatchHelper::ExpectCallToGetAffiliatedAndroidRealms( const PasswordStore::FormDigest& expected_observed_form, diff --git a/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliation_consumer.cc b/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliation_consumer.cc index f55a883799c..5b86f99380b 100644 --- a/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliation_consumer.cc +++ b/chromium/components/password_manager/core/browser/android_affiliation/mock_affiliation_consumer.cc @@ -13,8 +13,7 @@ MockAffiliationConsumer::MockAffiliationConsumer() { EXPECT_CALL(*this, OnResultCallback(testing::_, testing::_)).Times(0); } -MockAffiliationConsumer::~MockAffiliationConsumer() { -} +MockAffiliationConsumer::~MockAffiliationConsumer() = default; void MockAffiliationConsumer::ExpectSuccessWithResult( const AffiliatedFacets& expected_result) { diff --git a/chromium/components/password_manager/core/browser/browser_save_password_progress_logger.cc b/chromium/components/password_manager/core/browser/browser_save_password_progress_logger.cc index 88c18f259f4..4452b7f1e8a 100644 --- a/chromium/components/password_manager/core/browser/browser_save_password_progress_logger.cc +++ b/chromium/components/password_manager/core/browser/browser_save_password_progress_logger.cc @@ -285,7 +285,7 @@ void BrowserSavePasswordProgressLogger::LogPasswordForm( GetStringFromID(FormSchemeToStringID(form.scheme))); log.SetString(GetStringFromID(STRING_SIGNON_REALM), ScrubURL(GURL(form.signon_realm))); - log.SetString(GetStringFromID(STRING_ORIGIN), ScrubURL(form.origin)); + log.SetString(GetStringFromID(STRING_ORIGIN), ScrubURL(form.url)); log.SetString(GetStringFromID(STRING_ACTION), ScrubURL(form.action)); log.SetString(GetStringFromID(STRING_USERNAME_ELEMENT), ScrubElementID(form.username_element)); diff --git a/chromium/components/password_manager/core/browser/bulk_leak_check_service.cc b/chromium/components/password_manager/core/browser/bulk_leak_check_service.cc index 7870a318b1b..4d29976be2c 100644 --- a/chromium/components/password_manager/core/browser/bulk_leak_check_service.cc +++ b/chromium/components/password_manager/core/browser/bulk_leak_check_service.cc @@ -134,6 +134,18 @@ size_t BulkLeakCheckService::GetPendingChecksCount() const { return bulk_leak_check_ ? bulk_leak_check_->GetPendingChecksCount() : 0; } +BulkLeakCheckService::State BulkLeakCheckService::GetState() const { + return state_; +} + +void BulkLeakCheckService::AddObserver(Observer* obs) { + observers_.AddObserver(obs); +} + +void BulkLeakCheckService::RemoveObserver(Observer* obs) { + observers_.RemoveObserver(obs); +} + void BulkLeakCheckService::Shutdown() { observers_.Clear(); metrics_reporter_.reset(); diff --git a/chromium/components/password_manager/core/browser/bulk_leak_check_service.h b/chromium/components/password_manager/core/browser/bulk_leak_check_service.h index 04229822a96..29130923861 100644 --- a/chromium/components/password_manager/core/browser/bulk_leak_check_service.h +++ b/chromium/components/password_manager/core/browser/bulk_leak_check_service.h @@ -11,7 +11,7 @@ #include "base/memory/scoped_refptr.h" #include "base/observer_list.h" #include "base/observer_list_types.h" -#include "components/keyed_service/core/keyed_service.h" +#include "components/password_manager/core/browser/bulk_leak_check_service_interface.h" #include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h" #include "components/password_manager/core/browser/leak_detection/leak_detection_check_factory.h" #include "components/password_manager/core/browser/leak_detection/leak_detection_delegate_interface.h" @@ -26,68 +26,32 @@ class IdentityManager; namespace password_manager { -class BulkLeakCheck; - // The service that allows to check arbitrary number of passwords against the // database of leaked credentials. -class BulkLeakCheckService : public KeyedService, - public BulkLeakCheckDelegateInterface { +class BulkLeakCheckService : public BulkLeakCheckDelegateInterface, + public BulkLeakCheckServiceInterface { public: - enum class State { - // The service is idle and there was no previous error. - kIdle, - // The service is checking some credentials. - kRunning, - - // Those below are error states. On any error the current job is aborted. - // The error is sticky until next CheckUsernamePasswordPairs() call. - - // Cancel() aborted the running check. - kCanceled, - // The user isn't signed-in to Chrome. - kSignedOut, - // Error obtaining an access token. - kTokenRequestFailure, - // Error in hashing/encrypting for the request. - kHashingFailure, - // Error related to network. - kNetworkError, - // Error related to the password leak Google service. - kServiceError, - // Error related to the quota limit of the password leak Google service. - kQuotaLimit, - }; - - class Observer : public base::CheckedObserver { - public: - // BulkLeakCheckService changed its state. - virtual void OnStateChanged(State state) = 0; - - // Called when |credential| is analyzed. - virtual void OnCredentialDone(const LeakCheckCredential& credential, - IsLeaked is_leaked) = 0; - }; - BulkLeakCheckService( signin::IdentityManager* identity_manager, scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory); ~BulkLeakCheckService() override; // Starts the checks or appends |credentials| to the existing queue. - void CheckUsernamePasswordPairs(std::vector<LeakCheckCredential> credentials); + void CheckUsernamePasswordPairs( + std::vector<LeakCheckCredential> credentials) override; // Stops all the current checks immediately. - void Cancel(); + void Cancel() override; // Returns number of pending passwords to be checked. - size_t GetPendingChecksCount() const; + size_t GetPendingChecksCount() const override; // Returns the current state of the service. - State state() const { return state_; } + State GetState() const override; - void AddObserver(Observer* obs) { observers_.AddObserver(obs); } + void AddObserver(Observer* obs) override; - void RemoveObserver(Observer* obs) { observers_.RemoveObserver(obs); } + void RemoveObserver(Observer* obs) override; // KeyedService: void Shutdown() override; @@ -124,6 +88,7 @@ class BulkLeakCheckService : public KeyedService, std::unique_ptr<MetricsReporter> metrics_reporter_; State state_ = State::kIdle; + base::ObserverList<Observer> observers_; }; diff --git a/chromium/components/password_manager/core/browser/bulk_leak_check_service_interface.cc b/chromium/components/password_manager/core/browser/bulk_leak_check_service_interface.cc new file mode 100644 index 00000000000..9cc9c70308e --- /dev/null +++ b/chromium/components/password_manager/core/browser/bulk_leak_check_service_interface.cc @@ -0,0 +1,13 @@ +// Copyright 2020 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/bulk_leak_check_service_interface.h" + +namespace password_manager { + +BulkLeakCheckServiceInterface::BulkLeakCheckServiceInterface() = default; + +BulkLeakCheckServiceInterface::~BulkLeakCheckServiceInterface() = default; + +} // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/bulk_leak_check_service_interface.h b/chromium/components/password_manager/core/browser/bulk_leak_check_service_interface.h new file mode 100644 index 00000000000..f471ef9e089 --- /dev/null +++ b/chromium/components/password_manager/core/browser/bulk_leak_check_service_interface.h @@ -0,0 +1,90 @@ +// Copyright 2020 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. + +#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BULK_LEAK_CHECK_SERVICE_INTERFACE_H_ +#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BULK_LEAK_CHECK_SERVICE_INTERFACE_H_ + +#include <memory> +#include <vector> + +#include "base/observer_list_types.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h" +#include "components/password_manager/core/browser/leak_detection/leak_detection_delegate_interface.h" + +namespace password_manager { + +// The interface for a service that allows to check arbitrary number of +// passwords against the database of leaked credentials. +class BulkLeakCheckServiceInterface : public KeyedService { + public: + // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.password_check + // GENERATED_JAVA_CLASS_NAME_OVERRIDE: BulkLeakCheckServiceState + enum class State { + // The service is idle and there was no previous error. + kIdle, + // The service is checking some credentials. + kRunning, + + // Those below are error states. On any error the current job is aborted. + // The error is sticky until next CheckUsernamePasswordPairs() call. + + // Cancel() aborted the running check. + kCanceled, + // The user isn't signed-in to Chrome. + kSignedOut, + // Error obtaining an access token. + kTokenRequestFailure, + // Error in hashing/encrypting for the request. + kHashingFailure, + // Error related to network. + kNetworkError, + // Error related to the password leak Google service. + kServiceError, + // Error related to the quota limit of the password leak Google service. + kQuotaLimit, + }; + + class Observer : public base::CheckedObserver { + public: + // BulkLeakCheckService changed its state. + virtual void OnStateChanged(State state) = 0; + + // Called when |credential| is analyzed. + virtual void OnCredentialDone(const LeakCheckCredential& credential, + IsLeaked is_leaked) = 0; + }; + + BulkLeakCheckServiceInterface(); + ~BulkLeakCheckServiceInterface() override; + + // Not copyable or movable + BulkLeakCheckServiceInterface(const BulkLeakCheckServiceInterface&) = delete; + BulkLeakCheckServiceInterface& operator=( + const BulkLeakCheckServiceInterface&) = delete; + BulkLeakCheckServiceInterface(BulkLeakCheckServiceInterface&&) = delete; + BulkLeakCheckServiceInterface& operator=(BulkLeakCheckServiceInterface&&) = + delete; + + // Starts the checks or appends |credentials| to the existing queue. + virtual void CheckUsernamePasswordPairs( + std::vector<LeakCheckCredential> credentials) = 0; + + // Stops all the current checks immediately. + virtual void Cancel() = 0; + + // Returns number of pending passwords to be checked. + virtual size_t GetPendingChecksCount() const = 0; + + // Returns the current state of the service. + virtual State GetState() const = 0; + + virtual void AddObserver(Observer* obs) = 0; + + virtual void RemoveObserver(Observer* obs) = 0; +}; + +} // namespace password_manager + +#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_BULK_LEAK_CHECK_SERVICE_INTERFACE_H_ diff --git a/chromium/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc b/chromium/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc index 68518d78b58..aa406bb332b 100644 --- a/chromium/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc +++ b/chromium/components/password_manager/core/browser/bulk_leak_check_service_unittest.cc @@ -124,12 +124,12 @@ void BulkLeakCheckServiceTest::ConductLeakCheck( EXPECT_CALL(*weak_leak_check, GetPendingChecksCount) .WillRepeatedly(Return(0)); delegate->OnFinishedCredential(TestCredential(), is_leaked); - EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().GetState()); } TEST_F(BulkLeakCheckServiceTest, OnCreation) { EXPECT_EQ(0u, service().GetPendingChecksCount()); - EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().GetState()); EXPECT_THAT( histogram_tester().GetTotalCountsForPrefix("PasswordManager.BulkCheck"), @@ -141,7 +141,7 @@ TEST_F(BulkLeakCheckServiceTest, StartWithZeroPasswords) { service().AddObserver(&observer); service().CheckUsernamePasswordPairs({}); - EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); EXPECT_THAT( histogram_tester().GetTotalCountsForPrefix("PasswordManager.BulkCheck"), @@ -162,7 +162,7 @@ TEST_F(BulkLeakCheckServiceTest, Running) { EXPECT_CALL(observer, OnStateChanged(BulkLeakCheckService::State::kRunning)); service().CheckUsernamePasswordPairs(TestCredentials()); - EXPECT_EQ(BulkLeakCheckService::State::kRunning, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kRunning, service().GetState()); EXPECT_CALL(*weak_leak_check, GetPendingChecksCount) .WillRepeatedly(Return(10)); EXPECT_EQ(10u, service().GetPendingChecksCount()); @@ -189,7 +189,7 @@ TEST_F(BulkLeakCheckServiceTest, AppendRunning) { EXPECT_CALL(observer, OnStateChanged(BulkLeakCheckService::State::kRunning)); service().CheckUsernamePasswordPairs(TestCredentials()); - EXPECT_EQ(BulkLeakCheckService::State::kRunning, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kRunning, service().GetState()); EXPECT_CALL(*weak_leak_check, GetPendingChecksCount) .WillRepeatedly(Return(20)); EXPECT_EQ(20u, service().GetPendingChecksCount()); @@ -203,7 +203,7 @@ TEST_F(BulkLeakCheckServiceTest, FailedToCreateCheck) { .WillOnce(Return(ByMove(nullptr))); service().CheckUsernamePasswordPairs(TestCredentials()); - EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); EXPECT_THAT( histogram_tester().GetTotalCountsForPrefix("PasswordManager.BulkCheck"), @@ -223,7 +223,7 @@ TEST_F(BulkLeakCheckServiceTest, FailedToCreateCheckWithError) { OnStateChanged(BulkLeakCheckService::State::kSignedOut)); service().CheckUsernamePasswordPairs(TestCredentials()); - EXPECT_EQ(BulkLeakCheckService::State::kSignedOut, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kSignedOut, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); base::HistogramTester::CountsMap expected_counts; expected_counts["PasswordManager.BulkCheck.Error"] = 1; @@ -240,7 +240,7 @@ TEST_F(BulkLeakCheckServiceTest, CancelNothing) { service().Cancel(); - EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); EXPECT_THAT( histogram_tester().GetTotalCountsForPrefix("PasswordManager.BulkCheck"), @@ -259,7 +259,7 @@ TEST_F(BulkLeakCheckServiceTest, CancelSomething) { EXPECT_CALL(observer, OnStateChanged(BulkLeakCheckService::State::kCanceled)); service().Cancel(); - EXPECT_EQ(BulkLeakCheckService::State::kCanceled, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kCanceled, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); histogram_tester().ExpectUniqueSample( "PasswordManager.BulkCheck.CanceledCredentials", 2, 1); @@ -315,7 +315,7 @@ TEST_F(BulkLeakCheckServiceTest, CheckFinished) { IsLeaked(false))); delegate->OnFinishedCredential(TestCredential(), IsLeaked(false)); - EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); histogram_tester().ExpectUniqueSample( "PasswordManager.BulkCheck.CheckedCredentials", 2, 1); @@ -351,7 +351,7 @@ TEST_F(BulkLeakCheckServiceTest, CheckFinishedWithLeakedCredential) { } delegate->OnFinishedCredential(TestCredential(), IsLeaked(true)); - EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kIdle, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); histogram_tester().ExpectUniqueSample( "PasswordManager.BulkCheck.CheckedCredentials", 2, 1); @@ -407,7 +407,7 @@ TEST_F(BulkLeakCheckServiceTest, CheckFinishedWithError) { OnStateChanged(BulkLeakCheckService::State::kServiceError)); delegate->OnError(LeakDetectionError::kInvalidServerResponse); - EXPECT_EQ(BulkLeakCheckService::State::kServiceError, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kServiceError, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); base::HistogramTester::CountsMap expected_counts; expected_counts["PasswordManager.BulkCheck.Error"] = 1; @@ -434,7 +434,7 @@ TEST_F(BulkLeakCheckServiceTest, CheckFinishedWithQuotaLimit) { OnStateChanged(BulkLeakCheckService::State::kQuotaLimit)); delegate->OnError(LeakDetectionError::kQuotaLimit); - EXPECT_EQ(BulkLeakCheckService::State::kQuotaLimit, service().state()); + EXPECT_EQ(BulkLeakCheckService::State::kQuotaLimit, service().GetState()); EXPECT_EQ(0u, service().GetPendingChecksCount()); base::HistogramTester::CountsMap expected_counts; expected_counts["PasswordManager.BulkCheck.Error"] = 1; diff --git a/chromium/components/password_manager/core/browser/compromised_credentials_observer_unittest.cc b/chromium/components/password_manager/core/browser/compromised_credentials_observer_unittest.cc index 16ea59a9a9d..aa8e02e6376 100644 --- a/chromium/components/password_manager/core/browser/compromised_credentials_observer_unittest.cc +++ b/chromium/components/password_manager/core/browser/compromised_credentials_observer_unittest.cc @@ -25,8 +25,8 @@ constexpr char kUsernameNew[] = "ana"; autofill::PasswordForm TestForm(base::StringPiece username) { autofill::PasswordForm form; - form.origin = GURL(kSite); - form.signon_realm = form.origin.GetOrigin().spec(); + form.url = GURL(kSite); + form.signon_realm = form.url.GetOrigin().spec(); form.username_value = base::ASCIIToUTF16(username); form.password_value = base::ASCIIToUTF16("12345"); return form; diff --git a/chromium/components/password_manager/core/browser/compromised_credentials_table_unittest.cc b/chromium/components/password_manager/core/browser/compromised_credentials_table_unittest.cc index f87f65fd053..356c26bc91b 100644 --- a/chromium/components/password_manager/core/browser/compromised_credentials_table_unittest.cc +++ b/chromium/components/password_manager/core/browser/compromised_credentials_table_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <memory> + #include "components/password_manager/core/browser/compromised_credentials_table.h" #include "base/bind.h" @@ -42,8 +44,8 @@ class CompromisedCredentialsTableTest : public testing::Test { void ReloadDatabase() { base::FilePath file = temp_dir_.GetPath().AppendASCII("TestDatabase"); - db_.reset(new CompromisedCredentialsTable); - connection_.reset(new sql::Database); + db_ = std::make_unique<CompromisedCredentialsTable>(); + connection_ = std::make_unique<sql::Database>(); connection_->set_exclusive_locking(); ASSERT_TRUE(connection_->Open(file)); db_->Init(connection_.get()); diff --git a/chromium/components/password_manager/core/browser/credential_manager_impl.cc b/chromium/components/password_manager/core/browser/credential_manager_impl.cc index 97b8c2b0115..be90f0a8f0f 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_impl.cc +++ b/chromium/components/password_manager/core/browser/credential_manager_impl.cc @@ -3,6 +3,8 @@ // found in the LICENSE file. #include "components/password_manager/core/browser/credential_manager_impl.h" +#include <memory> + #include <string> #include "base/bind.h" @@ -29,22 +31,22 @@ CredentialManagerImpl::CredentialManagerImpl(PasswordManagerClient* client) client_->GetPrefs()); } -CredentialManagerImpl::~CredentialManagerImpl() {} +CredentialManagerImpl::~CredentialManagerImpl() = default; void CredentialManagerImpl::Store(const CredentialInfo& credential, StoreCallback callback) { DCHECK_NE(CredentialType::CREDENTIAL_TYPE_EMPTY, credential.type); + const url::Origin origin = GetOrigin(); if (password_manager_util::IsLoggingActive(client_)) { CredentialManagerLogger(client_->GetLogManager()) - .LogStoreCredential(GetLastCommittedURL(), credential.type); + .LogStoreCredential(origin, credential.type); } // Send acknowledge response back. std::move(callback).Run(); - const GURL origin = GetLastCommittedURL(); - if (!client_->IsSavingAndFillingEnabled(origin)) + if (!client_->IsSavingAndFillingEnabled(origin.GetURL())) return; client_->NotifyStorePasswordCalled(); @@ -56,9 +58,9 @@ void CredentialManagerImpl::Store(const CredentialInfo& credential, if (credential.type == CredentialType::CREDENTIAL_TYPE_PASSWORD) leak_delegate_.StartLeakCheck(*form); - std::string signon_realm = origin.GetOrigin().spec(); + std::string signon_realm = origin.GetURL().spec(); PasswordStore::FormDigest observed_digest( - autofill::PasswordForm::Scheme::kHtml, signon_realm, origin); + autofill::PasswordForm::Scheme::kHtml, signon_realm, origin.GetURL()); // Create a custom form fetcher without HTTP->HTTPS migration as the API is // only available on HTTPS origins. @@ -72,18 +74,18 @@ void CredentialManagerImpl::PreventSilentAccess( PreventSilentAccessCallback callback) { if (password_manager_util::IsLoggingActive(client_)) { CredentialManagerLogger(client_->GetLogManager()) - .LogPreventSilentAccess(GetLastCommittedURL()); + .LogPreventSilentAccess(GetOrigin()); } // Send acknowledge response back. std::move(callback).Run(); PasswordStore* store = GetPasswordStore(); - if (!store || !client_->IsSavingAndFillingEnabled(GetLastCommittedURL())) + if (!store || !client_->IsSavingAndFillingEnabled(GetOrigin().GetURL())) return; if (!pending_require_user_mediation_) { - pending_require_user_mediation_.reset( - new CredentialManagerPendingPreventSilentAccessTask(this)); + pending_require_user_mediation_ = + std::make_unique<CredentialManagerPendingPreventSilentAccessTask>(this); } pending_require_user_mediation_->AddOrigin(GetSynthesizedFormForOrigin()); } @@ -97,7 +99,7 @@ void CredentialManagerImpl::Get(CredentialMediationRequirement mediation, PasswordStore* store = GetPasswordStore(); if (password_manager_util::IsLoggingActive(client_)) { CredentialManagerLogger(client_->GetLogManager()) - .LogRequestCredential(GetLastCommittedURL(), mediation, federations); + .LogRequestCredential(GetOrigin(), mediation, federations); } if (pending_request_ || !store) { // Callback error. @@ -112,7 +114,7 @@ void CredentialManagerImpl::Get(CredentialMediationRequirement mediation, // Return an empty credential if the current page has TLS errors, or if the // page is being prerendered. - if (!client_->IsFillingEnabled(GetLastCommittedURL())) { + if (!client_->IsFillingEnabled(GetOrigin().GetURL())) { std::move(callback).Run(CredentialManagerError::SUCCESS, CredentialInfo()); LogCredentialManagerGetResult( metrics_util::CredentialManagerGetResult::kNone, mediation); @@ -126,6 +128,15 @@ void CredentialManagerImpl::Get(CredentialMediationRequirement mediation, metrics_util::CredentialManagerGetResult::kNoneIncognito, mediation); return; } + // Return an empty credential while autofill-assistant is running. + if (client_->GetAutofillAssistantMode() == AutofillAssistantMode::kRunning) { + // Callback with empty credential info. + std::move(callback).Run(CredentialManagerError::SUCCESS, CredentialInfo()); + LogCredentialManagerGetResult( + metrics_util::CredentialManagerGetResult::kNoneAutofillAssistant, + mediation); + return; + } // Return an empty credential if zero-click is required but disabled. if (mediation == CredentialMediationRequirement::kSilent && !IsZeroClickAllowed()) { @@ -136,9 +147,9 @@ void CredentialManagerImpl::Get(CredentialMediationRequirement mediation, return; } - pending_request_.reset(new CredentialManagerPendingRequestTask( + pending_request_ = std::make_unique<CredentialManagerPendingRequestTask>( this, base::BindOnce(&RunGetCallback, std::move(callback)), mediation, - include_passwords, federations)); + include_passwords, federations); // This will result in a callback to // PendingRequestTask::OnGetPasswordStoreResults(). GetPasswordStore()->GetLogins(GetSynthesizedFormForOrigin(), @@ -152,14 +163,13 @@ bool CredentialManagerImpl::IsZeroClickAllowed() const { PasswordStore::FormDigest CredentialManagerImpl::GetSynthesizedFormForOrigin() const { PasswordStore::FormDigest digest = {autofill::PasswordForm::Scheme::kHtml, - std::string(), - GetLastCommittedURL().GetOrigin()}; - digest.signon_realm = digest.origin.spec(); + std::string(), GetOrigin().GetURL()}; + digest.signon_realm = digest.url.spec(); return digest; } -GURL CredentialManagerImpl::GetOrigin() const { - return GetLastCommittedURL().GetOrigin(); +url::Origin CredentialManagerImpl::GetOrigin() const { + return client_->GetLastCommittedOrigin(); } void CredentialManagerImpl::SendCredential(SendCredentialCallback send_callback, @@ -168,7 +178,7 @@ void CredentialManagerImpl::SendCredential(SendCredentialCallback send_callback, if (password_manager_util::IsLoggingActive(client_)) { CredentialManagerLogger(client_->GetLogManager()) - .LogSendCredential(GetLastCommittedURL(), info.type); + .LogSendCredential(GetOrigin(), info.type); } std::move(send_callback).Run(info); pending_request_.reset(); @@ -221,7 +231,7 @@ void CredentialManagerImpl::DoneRequiringUserMediation() { void CredentialManagerImpl::OnProvisionalSaveComplete() { DCHECK(form_manager_); const autofill::PasswordForm& form = form_manager_->GetPendingCredentials(); - DCHECK(client_->IsSavingAndFillingEnabled(form.origin)); + DCHECK(client_->IsSavingAndFillingEnabled(form.url)); if (form_manager_->IsPendingCredentialsPublicSuffixMatch()) { // Having a credential with a PSL match implies there is no credential with @@ -256,8 +266,4 @@ void CredentialManagerImpl::OnProvisionalSaveComplete() { client_->PromptUserToSaveOrUpdatePassword(std::move(form_manager_), false); } -GURL CredentialManagerImpl::GetLastCommittedURL() const { - return client_->GetLastCommittedEntryURL(); -} - } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/credential_manager_impl.h b/chromium/components/password_manager/core/browser/credential_manager_impl.h index 909e21b4236..cf3f0b45e51 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_impl.h +++ b/chromium/components/password_manager/core/browser/credential_manager_impl.h @@ -61,7 +61,7 @@ class CredentialManagerImpl private: // CredentialManagerPendingRequestTaskDelegate: - GURL GetOrigin() const override; + url::Origin GetOrigin() const override; void SendCredential(SendCredentialCallback send_callback, const CredentialInfo& info) override; void SendPasswordForm(SendCredentialCallback send_callback, @@ -76,8 +76,6 @@ class CredentialManagerImpl // CredentialManagerPasswordFormManagerDelegate: void OnProvisionalSaveComplete() override; - GURL GetLastCommittedURL() const; - PasswordManagerClient* client_; // Set to false to disable automatic signing in. diff --git a/chromium/components/password_manager/core/browser/credential_manager_impl_unittest.cc b/chromium/components/password_manager/core/browser/credential_manager_impl_unittest.cc index 1984369ecd9..0d841083d93 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_impl_unittest.cc +++ b/chromium/components/password_manager/core/browser/credential_manager_impl_unittest.cc @@ -8,6 +8,8 @@ #include <algorithm> #include <map> +#include <memory> + #include <string> #include <tuple> @@ -26,6 +28,7 @@ #include "components/password_manager/core/browser/stub_password_manager_client.h" #include "components/password_manager/core/browser/test_password_store.h" #include "components/password_manager/core/common/credential_manager_types.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_registry_simple.h" #include "components/prefs/testing_pref_service.h" @@ -67,15 +70,19 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { MOCK_METHOD1(PromptUserToSavePasswordPtr, void(PasswordFormManagerForUI*)); MOCK_METHOD3(PromptUserToChooseCredentialsPtr, bool(const std::vector<autofill::PasswordForm*>& local_forms, - const GURL& origin, + const url::Origin& origin, const CredentialsCallback& callback)); MOCK_METHOD3(PasswordWasAutofilled, void(const std::vector<const autofill::PasswordForm*>&, - const GURL&, + const url::Origin&, const std::vector<const autofill::PasswordForm*>*)); + MOCK_CONST_METHOD0(GetAutofillAssistantMode, AutofillAssistantMode()); - explicit MockPasswordManagerClient(PasswordStore* store) - : store_(store), password_manager_(this) { + explicit MockPasswordManagerClient(PasswordStore* profile_store, + PasswordStore* account_store) + : profile_store_(profile_store), + account_store_(account_store), + password_manager_(this) { prefs_ = std::make_unique<TestingPrefServiceSimple>(); prefs_->registry()->RegisterBooleanPref(prefs::kCredentialsEnableAutosignin, true); @@ -103,7 +110,12 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { NotifyUserCouldBeAutoSignedInPtr(form.get()); } - PasswordStore* GetProfilePasswordStore() const override { return store_; } + PasswordStore* GetProfilePasswordStore() const override { + return profile_store_; + } + PasswordStore* GetAccountPasswordStore() const override { + return account_store_; + } PrefService* GetPrefs() const override { return prefs_.get(); } @@ -111,13 +123,13 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { return &password_manager_; } - const GURL& GetLastCommittedEntryURL() const override { - return last_committed_url_; + url::Origin GetLastCommittedOrigin() const override { + return url::Origin::Create(last_committed_url_); } bool PromptUserToChooseCredentials( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin, + const url::Origin& origin, const CredentialsCallback& callback) override { EXPECT_FALSE(local_forms.empty()); const autofill::PasswordForm* form = local_forms[0].get(); @@ -136,7 +148,7 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { void NotifyUserAutoSignin( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin) override { + const url::Origin& origin) override { EXPECT_FALSE(local_forms.empty()); NotifyUserAutoSigninPtr(); } @@ -158,7 +170,8 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { private: std::unique_ptr<TestingPrefServiceSimple> prefs_; - PasswordStore* store_; + PasswordStore* profile_store_; + PasswordStore* account_store_; std::unique_ptr<PasswordFormManagerForUI> manager_; PasswordManager password_manager_; GURL last_committed_url_{kTestWebOrigin}; @@ -191,14 +204,19 @@ GURL HttpURLFromHttps(const GURL& https_url) { class CredentialManagerImplTest : public testing::Test { public: - CredentialManagerImplTest() {} + CredentialManagerImplTest() = default; void SetUp() override { store_ = new TestPasswordStore; - store_->Init(nullptr); - client_.reset( - new testing::NiceMock<MockPasswordManagerClient>(store_.get())); - cm_service_impl_.reset(new CredentialManagerImpl(client_.get())); + store_->Init(/*prefs=*/nullptr); + if (base::FeatureList::IsEnabled( + features::kEnablePasswordsAccountStorage)) { + account_store_ = new TestPasswordStore; + ASSERT_TRUE(account_store_->Init(/*prefs=*/nullptr)); + } + client_ = std::make_unique<testing::NiceMock<MockPasswordManagerClient>>( + store_.get(), account_store_.get()); + cm_service_impl_ = std::make_unique<CredentialManagerImpl>(client_.get()); ON_CALL(*client_, IsSavingAndFillingEnabled(_)) .WillByDefault(testing::Return(true)); ON_CALL(*client_, IsFillingEnabled(_)).WillByDefault(testing::Return(true)); @@ -208,15 +226,15 @@ class CredentialManagerImplTest : public testing::Test { form_.display_name = base::ASCIIToUTF16("Display Name"); form_.icon_url = GURL("https://example.com/icon.png"); form_.password_value = base::ASCIIToUTF16("Password"); - form_.origin = client_->GetLastCommittedEntryURL(); - form_.signon_realm = form_.origin.GetOrigin().spec(); + form_.url = client_->GetLastCommittedOrigin().GetURL(); + form_.signon_realm = form_.url.GetOrigin().spec(); form_.scheme = autofill::PasswordForm::Scheme::kHtml; form_.skip_zero_click = false; affiliated_form1_.username_value = base::ASCIIToUTF16("Affiliated 1"); affiliated_form1_.display_name = base::ASCIIToUTF16("Display Name"); affiliated_form1_.password_value = base::ASCIIToUTF16("Password"); - affiliated_form1_.origin = GURL(); + affiliated_form1_.url = GURL(); affiliated_form1_.signon_realm = kTestAndroidRealm1; affiliated_form1_.scheme = autofill::PasswordForm::Scheme::kHtml; affiliated_form1_.skip_zero_click = false; @@ -224,7 +242,7 @@ class CredentialManagerImplTest : public testing::Test { affiliated_form2_.username_value = base::ASCIIToUTF16("Affiliated 2"); affiliated_form2_.display_name = base::ASCIIToUTF16("Display Name"); affiliated_form2_.password_value = base::ASCIIToUTF16("Password"); - affiliated_form2_.origin = GURL(); + affiliated_form2_.url = GURL(); affiliated_form2_.signon_realm = kTestAndroidRealm2; affiliated_form2_.scheme = autofill::PasswordForm::Scheme::kHtml; affiliated_form2_.skip_zero_click = false; @@ -232,26 +250,24 @@ class CredentialManagerImplTest : public testing::Test { origin_path_form_.username_value = base::ASCIIToUTF16("Username 2"); origin_path_form_.display_name = base::ASCIIToUTF16("Display Name 2"); origin_path_form_.password_value = base::ASCIIToUTF16("Password 2"); - origin_path_form_.origin = GURL("https://example.com/path"); - origin_path_form_.signon_realm = - origin_path_form_.origin.GetOrigin().spec(); + origin_path_form_.url = GURL("https://example.com/path"); + origin_path_form_.signon_realm = origin_path_form_.url.GetOrigin().spec(); origin_path_form_.scheme = autofill::PasswordForm::Scheme::kHtml; origin_path_form_.skip_zero_click = false; subdomain_form_.username_value = base::ASCIIToUTF16("Username 2"); subdomain_form_.display_name = base::ASCIIToUTF16("Display Name 2"); subdomain_form_.password_value = base::ASCIIToUTF16("Password 2"); - subdomain_form_.origin = GURL("https://subdomain.example.com/path"); - subdomain_form_.signon_realm = subdomain_form_.origin.GetOrigin().spec(); + subdomain_form_.url = GURL("https://subdomain.example.com/path"); + subdomain_form_.signon_realm = subdomain_form_.url.GetOrigin().spec(); subdomain_form_.scheme = autofill::PasswordForm::Scheme::kHtml; subdomain_form_.skip_zero_click = false; cross_origin_form_.username_value = base::ASCIIToUTF16("Username"); cross_origin_form_.display_name = base::ASCIIToUTF16("Display Name"); cross_origin_form_.password_value = base::ASCIIToUTF16("Password"); - cross_origin_form_.origin = GURL("https://example.net/"); - cross_origin_form_.signon_realm = - cross_origin_form_.origin.GetOrigin().spec(); + cross_origin_form_.url = GURL("https://example.net/"); + cross_origin_form_.signon_realm = cross_origin_form_.url.GetOrigin().spec(); cross_origin_form_.scheme = autofill::PasswordForm::Scheme::kHtml; cross_origin_form_.skip_zero_click = false; @@ -262,6 +278,9 @@ class CredentialManagerImplTest : public testing::Test { void TearDown() override { cm_service_impl_.reset(); + if (account_store_) { + account_store_->ShutdownOnUIThread(); + } store_->ShutdownOnUIThread(); // It's needed to cleanup the password store asynchronously. @@ -357,6 +376,7 @@ class CredentialManagerImplTest : public testing::Test { autofill::PasswordForm subdomain_form_; autofill::PasswordForm cross_origin_form_; scoped_refptr<TestPasswordStore> store_; + scoped_refptr<TestPasswordStore> account_store_; std::unique_ptr<testing::NiceMock<MockPasswordManagerClient>> client_; std::unique_ptr<CredentialManagerImpl> cm_service_impl_; }; @@ -400,7 +420,7 @@ TEST_F(CredentialManagerImplTest, CredentialManagerOnStore) { EXPECT_EQ(form_.username_value, new_form.username_value); EXPECT_EQ(form_.display_name, new_form.display_name); EXPECT_EQ(form_.password_value, new_form.password_value); - EXPECT_EQ(form_.origin, new_form.origin); + EXPECT_EQ(form_.url, new_form.url); EXPECT_EQ(form_.signon_realm, new_form.signon_realm); EXPECT_TRUE(new_form.federation_origin.opaque()); EXPECT_EQ(form_.icon_url, new_form.icon_url); @@ -431,7 +451,7 @@ TEST_F(CredentialManagerImplTest, CredentialManagerOnStoreFederated) { EXPECT_EQ(form_.username_value, new_form.username_value); EXPECT_EQ(form_.display_name, new_form.display_name); EXPECT_EQ(form_.password_value, new_form.password_value); - EXPECT_EQ(form_.origin, new_form.origin); + EXPECT_EQ(form_.url, new_form.url); EXPECT_EQ(form_.signon_realm, new_form.signon_realm); EXPECT_EQ(form_.federation_origin, new_form.federation_origin); EXPECT_EQ(form_.icon_url, new_form.icon_url); @@ -658,7 +678,7 @@ TEST_F(CredentialManagerImplTest, CredentialManagerGetOverwriteZeroClick) { form_.skip_zero_click = true; form_.username_element = base::ASCIIToUTF16("username-element"); form_.password_element = base::ASCIIToUTF16("password-element"); - form_.origin = GURL("https://example.com/old_form.html"); + form_.url = GURL("https://example.com/old_form.html"); store_->AddLogin(form_); RunAllPendingTasks(); @@ -686,7 +706,7 @@ TEST_F(CredentialManagerImplTest, CredentialManagerGetOverwriteZeroClick) { TEST_F(CredentialManagerImplTest, CredentialManagerSignInWithSavingDisabledForCurrentPage) { CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); - EXPECT_CALL(*client_, IsSavingAndFillingEnabled(form_.origin)) + EXPECT_CALL(*client_, IsSavingAndFillingEnabled(form_.url)) .WillRepeatedly(testing::Return(false)); EXPECT_CALL(*client_, PromptUserToSavePasswordPtr(_)) .Times(testing::Exactly(0)); @@ -917,7 +937,7 @@ TEST_F(CredentialManagerImplTest, federated.federation_origin = url::Origin::Create(GURL("https://google.com/")); federated.signon_realm = - "federation://" + federated.origin.host() + "/google.com"; + "federation://" + federated.url.host() + "/google.com"; store_->AddLogin(federated); EXPECT_CALL(*client_, @@ -932,7 +952,7 @@ TEST_F(CredentialManagerImplTest, CredentialManagerError error; base::Optional<CredentialInfo> credential; std::vector<GURL> federations; - federations.push_back(GURL("https://google.com/")); + federations.emplace_back("https://google.com/"); CallGet(CredentialMediationRequirement::kOptional, true, federations, base::BindOnce(&GetCredentialCallback, &called, &error, &credential)); @@ -1022,7 +1042,7 @@ TEST_F(CredentialManagerImplTest, client_->set_first_run_seen(true); std::vector<GURL> federations; - federations.push_back(GURL("https://example.com/")); + federations.emplace_back("https://example.com/"); EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(_)).Times(0); @@ -1039,7 +1059,7 @@ TEST_F(CredentialManagerImplTest, client_->set_first_run_seen(true); std::vector<GURL> federations; - federations.push_back(GURL("https://not-example.com/")); + federations.emplace_back("https://not-example.com/"); EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(_)).Times(0); @@ -1099,7 +1119,7 @@ TEST_F(CredentialManagerImplTest, std::make_unique<MockAffiliatedMatchHelper>()); std::vector<GURL> federations; - federations.push_back(GURL("https://example.com/")); + federations.emplace_back("https://example.com/"); std::vector<std::string> affiliated_realms; affiliated_realms.push_back(kTestAndroidRealm1); @@ -1123,7 +1143,7 @@ TEST_F(CredentialManagerImplTest, std::make_unique<MockAffiliatedMatchHelper>()); std::vector<GURL> federations; - federations.push_back(GURL("https://not-example.com/")); + federations.emplace_back("https://not-example.com/"); std::vector<std::string> affiliated_realms; affiliated_realms.push_back(kTestAndroidRealm1); @@ -1332,6 +1352,21 @@ TEST_F(CredentialManagerImplTest, IncognitoZeroClickRequestCredential) { federations, CredentialType::CREDENTIAL_TYPE_EMPTY); } +TEST_F(CredentialManagerImplTest, AutofillAssistantZeroClickRequestCredential) { + store_->AddLogin(form_); + + std::vector<GURL> federations; + EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr) + .Times(testing::Exactly(0)); + EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(testing::Exactly(0)); + EXPECT_CALL(*client_, IsIncognito()).WillRepeatedly(testing::Return(false)); + EXPECT_CALL(*client_, GetAutofillAssistantMode()) + .WillRepeatedly(testing::Return(AutofillAssistantMode::kRunning)); + + ExpectCredentialType(CredentialMediationRequirement::kOptional, true, + federations, CredentialType::CREDENTIAL_TYPE_EMPTY); +} + TEST_F(CredentialManagerImplTest, ZeroClickWithAffiliatedFormInPasswordStore) { // Insert the affiliated form into the store, and mock out the association // with the current origin. As it's the only form matching the origin, it @@ -1392,8 +1427,8 @@ TEST_F(CredentialManagerImplTest, static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) ->ExpectCallToGetAffiliatedAndroidRealms(digest, affiliated_realms); - digest.origin = HttpURLFromHttps(digest.origin); - digest.signon_realm = digest.origin.spec(); + digest.url = HttpURLFromHttps(digest.url); + digest.signon_realm = digest.url.spec(); // The second call happens for HTTP as the migration is triggered. static_cast<MockAffiliatedMatchHelper*>(store_->affiliated_match_helper()) ->ExpectCallToGetAffiliatedAndroidRealms(digest, affiliated_realms); @@ -1436,7 +1471,7 @@ TEST_F(CredentialManagerImplTest, ZeroClickWithPSLCredential) { TEST_F(CredentialManagerImplTest, ZeroClickWithPSLAndNormalCredentials) { form_.password_value.clear(); form_.federation_origin = url::Origin::Create(GURL("https://google.com/")); - form_.signon_realm = "federation://" + form_.origin.host() + "/google.com"; + form_.signon_realm = "federation://" + form_.url.host() + "/google.com"; form_.skip_zero_click = false; store_->AddLogin(form_); store_->AddLogin(subdomain_form_); @@ -1450,8 +1485,8 @@ TEST_F(CredentialManagerImplTest, ZeroClickWithPSLAndNormalCredentials) { TEST_F(CredentialManagerImplTest, ZeroClickAfterMigratingHttpCredential) { // There is an http credential saved. It should be migrated and used for auto // sign-in. - form_.origin = HttpURLFromHttps(form_.origin); - form_.signon_realm = form_.origin.GetOrigin().spec(); + form_.url = HttpURLFromHttps(form_.url); + form_.signon_realm = form_.url.GetOrigin().spec(); // That is the default value for old credentials. form_.skip_zero_click = true; store_->AddLogin(form_); @@ -1496,7 +1531,7 @@ TEST_F(CredentialManagerImplTest, MediationRequiredPreventsAutoSignIn) { TEST_F(CredentialManagerImplTest, GetSynthesizedFormForOrigin) { PasswordStore::FormDigest synthesized = cm_service_impl_->GetSynthesizedFormForOrigin(); - EXPECT_EQ(kTestWebOrigin, synthesized.origin.spec()); + EXPECT_EQ(kTestWebOrigin, synthesized.url.spec()); EXPECT_EQ(kTestWebOrigin, synthesized.signon_realm); EXPECT_EQ(autofill::PasswordForm::Scheme::kHtml, synthesized.scheme); } @@ -1504,8 +1539,8 @@ TEST_F(CredentialManagerImplTest, GetSynthesizedFormForOrigin) { TEST_F(CredentialManagerImplTest, GetBlacklistedPasswordCredential) { autofill::PasswordForm blacklisted; blacklisted.blacklisted_by_user = true; - blacklisted.origin = form_.origin; - blacklisted.signon_realm = blacklisted.origin.spec(); + blacklisted.url = form_.url; + blacklisted.signon_realm = blacklisted.url.spec(); // Deliberately use a wrong format with a non-empty username to simulate a // leak. See https://crbug.com/817754. blacklisted.username_value = base::ASCIIToUTF16("Username"); @@ -1537,7 +1572,7 @@ TEST_F(CredentialManagerImplTest, BlacklistPasswordCredential) { autofill::PasswordForm blacklisted; TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); blacklisted.blacklisted_by_user = true; - blacklisted.origin = form_.origin; + blacklisted.url = form_.url; blacklisted.signon_realm = form_.signon_realm; blacklisted.date_created = passwords[form_.signon_realm][0].date_created; EXPECT_THAT(passwords[form_.signon_realm], @@ -1563,11 +1598,11 @@ TEST_F(CredentialManagerImplTest, BlacklistFederatedCredential) { // Verify that the site is blacklisted. TestPasswordStore::PasswordMap passwords = store_->stored_passwords(); - ASSERT_TRUE(passwords.count(form_.origin.spec())); + ASSERT_TRUE(passwords.count(form_.url.spec())); autofill::PasswordForm blacklisted; blacklisted.blacklisted_by_user = true; - blacklisted.origin = form_.origin; - blacklisted.signon_realm = blacklisted.origin.spec(); + blacklisted.url = form_.url; + blacklisted.signon_realm = blacklisted.url.spec(); blacklisted.date_created = passwords[blacklisted.signon_realm][0].date_created; EXPECT_THAT(passwords[blacklisted.signon_realm], @@ -1577,8 +1612,8 @@ TEST_F(CredentialManagerImplTest, BlacklistFederatedCredential) { TEST_F(CredentialManagerImplTest, RespectBlacklistingPasswordCredential) { autofill::PasswordForm blacklisted; blacklisted.blacklisted_by_user = true; - blacklisted.origin = form_.origin; - blacklisted.signon_realm = blacklisted.origin.spec(); + blacklisted.url = form_.url; + blacklisted.signon_realm = blacklisted.url.spec(); store_->AddLogin(blacklisted); CredentialInfo info(form_, CredentialType::CREDENTIAL_TYPE_PASSWORD); @@ -1595,8 +1630,8 @@ TEST_F(CredentialManagerImplTest, RespectBlacklistingPasswordCredential) { TEST_F(CredentialManagerImplTest, RespectBlacklistingFederatedCredential) { autofill::PasswordForm blacklisted; blacklisted.blacklisted_by_user = true; - blacklisted.origin = form_.origin; - blacklisted.signon_realm = blacklisted.origin.spec(); + blacklisted.url = form_.url; + blacklisted.signon_realm = blacklisted.url.spec(); store_->AddLogin(blacklisted); form_.federation_origin = url::Origin::Create(GURL("https://example.com/")); @@ -1619,7 +1654,7 @@ TEST_F(CredentialManagerImplTest, federated.federation_origin = url::Origin::Create(GURL("https://google.com/")); federated.signon_realm = - "federation://" + federated.origin.host() + "/google.com"; + "federation://" + federated.url.host() + "/google.com"; store_->AddLogin(federated); form_.username_value = base::ASCIIToUTF16("username_value"); @@ -1635,7 +1670,7 @@ TEST_F(CredentialManagerImplTest, CredentialManagerError error; base::Optional<CredentialInfo> credential; std::vector<GURL> federations; - federations.push_back(GURL("https://google.com/")); + federations.emplace_back("https://google.com/"); CallGet(CredentialMediationRequirement::kSilent, true, federations, base::BindOnce(&GetCredentialCallback, &called, &error, &credential)); @@ -1671,7 +1706,7 @@ TEST_F(CredentialManagerImplTest, StorePasswordCredentialStartsLeakDetection) { auto check_instance = std::make_unique<MockLeakDetectionCheck>(); EXPECT_CALL(*check_instance, - Start(form_.origin, form_.username_value, form_.password_value)); + Start(form_.url, form_.username_value, form_.password_value)); EXPECT_CALL(*weak_factory, TryCreateLeakCheck) .WillOnce(testing::Return(testing::ByMove(std::move(check_instance)))); CallStore({form_, CredentialType::CREDENTIAL_TYPE_PASSWORD}, diff --git a/chromium/components/password_manager/core/browser/credential_manager_logger.cc b/chromium/components/password_manager/core/browser/credential_manager_logger.cc index cc7373ced5b..22c27ecfa76 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_logger.cc +++ b/chromium/components/password_manager/core/browser/credential_manager_logger.cc @@ -21,11 +21,10 @@ CredentialManagerLogger::CredentialManagerLogger( CredentialManagerLogger::~CredentialManagerLogger() = default; void CredentialManagerLogger::LogRequestCredential( - const GURL& url, + const url::Origin& origin, CredentialMediationRequirement mediation, const std::vector<GURL>& federations) { - std::string s("CM API get credentials: origin=" + - SavePasswordProgressLogger::ScrubURL(url)); + std::string s("CM API get credentials: origin=" + origin.Serialize()); s += ", mediation="; switch (mediation) { case CredentialMediationRequirement::kSilent: @@ -45,25 +44,23 @@ void CredentialManagerLogger::LogRequestCredential( log_manager_->LogTextMessage(s); } -void CredentialManagerLogger::LogSendCredential(const GURL& url, +void CredentialManagerLogger::LogSendCredential(const url::Origin& origin, CredentialType type) { - std::string s("CM API send a credential: origin=" + - SavePasswordProgressLogger::ScrubURL(url)); + std::string s("CM API send a credential: origin=" + origin.Serialize()); s += ", CredentialType=" + CredentialTypeToString(type); log_manager_->LogTextMessage(s); } -void CredentialManagerLogger::LogStoreCredential(const GURL& url, +void CredentialManagerLogger::LogStoreCredential(const url::Origin& origin, CredentialType type) { - std::string s("CM API save a credential: origin=" + - SavePasswordProgressLogger::ScrubURL(url)); + std::string s("CM API save a credential: origin=" + origin.Serialize()); s += ", CredentialType=" + CredentialTypeToString(type); log_manager_->LogTextMessage(s); } -void CredentialManagerLogger::LogPreventSilentAccess(const GURL& url) { - std::string s("CM API sign out: origin=" + - SavePasswordProgressLogger::ScrubURL(url)); +void CredentialManagerLogger::LogPreventSilentAccess( + const url::Origin& origin) { + std::string s("CM API sign out: origin=" + origin.Serialize()); log_manager_->LogTextMessage(s); } diff --git a/chromium/components/password_manager/core/browser/credential_manager_logger.h b/chromium/components/password_manager/core/browser/credential_manager_logger.h index 7febf4d1b63..5674ba48b48 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_logger.h +++ b/chromium/components/password_manager/core/browser/credential_manager_logger.h @@ -24,12 +24,12 @@ class CredentialManagerLogger { explicit CredentialManagerLogger(const autofill::LogManager*); ~CredentialManagerLogger(); - void LogRequestCredential(const GURL& url, + void LogRequestCredential(const url::Origin& url, CredentialMediationRequirement mediation, const std::vector<GURL>& federations); - void LogSendCredential(const GURL& url, CredentialType type); - void LogStoreCredential(const GURL& url, CredentialType type); - void LogPreventSilentAccess(const GURL& url); + void LogSendCredential(const url::Origin& origin, CredentialType type); + void LogStoreCredential(const url::Origin& origin, CredentialType type); + void LogPreventSilentAccess(const url::Origin& origin); private: // The LogManager to which logs can be sent for display. diff --git a/chromium/components/password_manager/core/browser/credential_manager_logger_unittest.cc b/chromium/components/password_manager/core/browser/credential_manager_logger_unittest.cc index 1a3f07a13ac..1aac5762195 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_logger_unittest.cc +++ b/chromium/components/password_manager/core/browser/credential_manager_logger_unittest.cc @@ -15,8 +15,8 @@ using ::testing::AllOf; using ::testing::HasSubstr; using ::testing::Return; -const char kSiteOrigin[] = "https://example.com"; -const char kFederationOrigin[] = "https://google.com"; +constexpr char kSiteOrigin[] = "https://example.com"; +constexpr char kFederationOrigin[] = "https://google.com"; class MockLogManager : public autofill::StubLogManager { public: @@ -53,26 +53,26 @@ TEST_F(CredentialManagerLoggerTest, LogRequestCredential) { EXPECT_CALL(log_manager(), LogTextMessage( AllOf(HasSubstr(kSiteOrigin), HasSubstr(kFederationOrigin)))); - logger().LogRequestCredential(GURL(kSiteOrigin), + logger().LogRequestCredential(url::Origin::Create(GURL(kSiteOrigin)), CredentialMediationRequirement::kSilent, {GURL(kFederationOrigin)}); } TEST_F(CredentialManagerLoggerTest, LogSendCredential) { EXPECT_CALL(log_manager(), LogTextMessage(HasSubstr(kSiteOrigin))); - logger().LogSendCredential(GURL(kSiteOrigin), + logger().LogSendCredential(url::Origin::Create(GURL(kSiteOrigin)), CredentialType::CREDENTIAL_TYPE_PASSWORD); } TEST_F(CredentialManagerLoggerTest, LogStoreCredential) { EXPECT_CALL(log_manager(), LogTextMessage(HasSubstr(kSiteOrigin))); - logger().LogStoreCredential(GURL(kSiteOrigin), + logger().LogStoreCredential(url::Origin::Create(GURL(kSiteOrigin)), CredentialType::CREDENTIAL_TYPE_PASSWORD); } TEST_F(CredentialManagerLoggerTest, LogPreventSilentAccess) { EXPECT_CALL(log_manager(), LogTextMessage(HasSubstr(kSiteOrigin))); - logger().LogPreventSilentAccess(GURL(kSiteOrigin)); + logger().LogPreventSilentAccess(url::Origin::Create(GURL(kSiteOrigin))); } } // namespace diff --git a/chromium/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc b/chromium/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc index fb2c2003cfb..4bac5880558 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc @@ -56,7 +56,7 @@ class MockFormSaver : public StubFormSaver { }; MATCHER_P(FormMatches, form, "") { - return form.signon_realm == arg.signon_realm && form.origin == arg.origin && + return form.signon_realm == arg.signon_realm && form.url == arg.url && form.username_value == arg.username_value && form.password_value == arg.password_value && form.scheme == arg.scheme && form.type == arg.type; @@ -67,7 +67,7 @@ MATCHER_P(FormMatches, form, "") { class CredentialManagerPasswordFormManagerTest : public testing::Test { public: CredentialManagerPasswordFormManagerTest() { - form_to_save_.origin = GURL("https://example.com/path"); + form_to_save_.url = GURL("https://example.com/path"); form_to_save_.signon_realm = "https://example.com/"; form_to_save_.username_value = ASCIIToUTF16("user1"); form_to_save_.password_value = ASCIIToUTF16("pass1"); @@ -147,7 +147,7 @@ TEST_F(CredentialManagerPasswordFormManagerTest, {&saved_match}); EXPECT_TRUE(form_manager->IsNewLogin()); EXPECT_TRUE(form_manager->is_submitted()); - EXPECT_EQ(form_to_save_.origin, form_manager->GetOrigin()); + EXPECT_EQ(form_to_save_.url, form_manager->GetURL()); EXPECT_CALL(form_saver, Save(FormMatches(form_to_save_), _, _)); form_manager->Save(); diff --git a/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.cc b/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.cc index 7cf4bb3f5c9..e6e91ca69e8 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.cc +++ b/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.cc @@ -105,7 +105,7 @@ CredentialManagerPendingRequestTask::~CredentialManagerPendingRequestTask() = void CredentialManagerPendingRequestTask::OnGetPasswordStoreResults( std::vector<std::unique_ptr<autofill::PasswordForm>> results) { // localhost is a secure origin but not https. - if (results.empty() && origin_.SchemeIs(url::kHttpsScheme)) { + if (results.empty() && origin_.scheme() == url::kHttpsScheme) { // Try to migrate the HTTP passwords and process them later. http_migrator_ = std::make_unique<HttpPasswordStoreMigrator>( origin_, delegate_->client(), this); @@ -150,7 +150,7 @@ void CredentialManagerPendingRequestTask::ProcessForms( // GURL definition: scheme, host, and port. // So we can't compare them directly. if (form->is_affiliation_based_match || - form->origin.GetOrigin() == origin_.GetOrigin()) { + url::Origin::Create(form->url) == origin_) { local_results.push_back(std::move(form)); } else if (form->is_public_suffix_match) { psl_results.push_back(std::move(form)); diff --git a/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.h b/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.h index 787cadcd7c8..bb1d05c4c88 100644 --- a/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.h +++ b/chromium/components/password_manager/core/browser/credential_manager_pending_request_task.h @@ -17,6 +17,7 @@ #include "components/password_manager/core/browser/password_store_consumer.h" #include "components/password_manager/core/common/credential_manager_types.h" #include "url/gurl.h" +#include "url/origin.h" namespace autofill { struct PasswordForm; @@ -38,7 +39,7 @@ class CredentialManagerPendingRequestTaskDelegate { virtual bool IsZeroClickAllowed() const = 0; // Retrieves the current page origin. - virtual GURL GetOrigin() const = 0; + virtual url::Origin GetOrigin() const = 0; // Returns the PasswordManagerClient. virtual PasswordManagerClient* client() const = 0; @@ -67,7 +68,7 @@ class CredentialManagerPendingRequestTask const std::vector<GURL>& request_federations); ~CredentialManagerPendingRequestTask() override; - const GURL& origin() const { return origin_; } + const url::Origin& origin() const { return origin_; } // PasswordStoreConsumer: void OnGetPasswordStoreResults( @@ -84,7 +85,7 @@ class CredentialManagerPendingRequestTask CredentialManagerPendingRequestTaskDelegate* delegate_; // Weak; SendCredentialCallback send_callback_; const CredentialMediationRequirement mediation_; - const GURL origin_; + const url::Origin origin_; const bool include_passwords_; std::set<std::string> federations_; diff --git a/chromium/components/password_manager/core/browser/export/OWNERS b/chromium/components/password_manager/core/browser/export/OWNERS new file mode 100644 index 00000000000..1d447d61f5c --- /dev/null +++ b/chromium/components/password_manager/core/browser/export/OWNERS @@ -0,0 +1,4 @@ +cfroussios@chromium.org + +# COMPONENT: UI>Browser>Passwords +# TEAM: chromium-dev@chromium.org diff --git a/chromium/components/password_manager/core/browser/export/csv_writer_unittest.cc b/chromium/components/password_manager/core/browser/export/csv_writer_unittest.cc index eb89d5a3c6d..c53ba9d6f06 100644 --- a/chromium/components/password_manager/core/browser/export/csv_writer_unittest.cc +++ b/chromium/components/password_manager/core/browser/export/csv_writer_unittest.cc @@ -18,7 +18,7 @@ namespace password_manager { class CSVWriterTest : public testing::Test { public: - CSVWriterTest() {} + CSVWriterTest() = default; void SetUp() override { column_names_.push_back("foo"); diff --git a/chromium/components/password_manager/core/browser/export/password_csv_writer.cc b/chromium/components/password_manager/core/browser/export/password_csv_writer.cc index 7d05e3e735c..5d893456bab 100644 --- a/chromium/components/password_manager/core/browser/export/password_csv_writer.cc +++ b/chromium/components/password_manager/core/browser/export/password_csv_writer.cc @@ -44,10 +44,10 @@ std::string PasswordCSVWriter::SerializePasswords( std::map<std::string, std::string> PasswordCSVWriter::PasswordFormToRecord( const PasswordForm& form) { std::map<std::string, std::string> record; - record[kUrlColumnName] = form.origin.spec(); + record[kUrlColumnName] = form.url.spec(); record[kUsernameColumnName] = base::UTF16ToUTF8(form.username_value); record[kPasswordColumnName] = base::UTF16ToUTF8(form.password_value); - record[kTitleColumnName] = form.origin.host(); + record[kTitleColumnName] = form.url.host(); return record; } diff --git a/chromium/components/password_manager/core/browser/export/password_csv_writer_unittest.cc b/chromium/components/password_manager/core/browser/export/password_csv_writer_unittest.cc index e431f68f5e0..2b8d665b1a7 100644 --- a/chromium/components/password_manager/core/browser/export/password_csv_writer_unittest.cc +++ b/chromium/components/password_manager/core/browser/export/password_csv_writer_unittest.cc @@ -23,7 +23,7 @@ namespace password_manager { namespace { MATCHER_P3(FormHasOriginUsernamePassword, origin, username, password, "") { - return arg.signon_realm == origin && arg.origin == GURL(origin) && + return arg.signon_realm == origin && arg.url == GURL(origin) && arg.username_value == base::UTF8ToUTF16(username) && arg.password_value == base::UTF8ToUTF16(password); } @@ -42,7 +42,7 @@ TEST(PasswordCSVWriterTest, SerializePasswords_ZeroPasswords) { TEST(PasswordCSVWriterTest, SerializePasswords_SinglePassword) { std::vector<std::unique_ptr<PasswordForm>> passwords; PasswordForm form; - form.origin = GURL("http://example.com"); + form.url = GURL("http://example.com"); form.username_value = base::UTF8ToUTF16("Someone"); form.password_value = base::UTF8ToUTF16("Secret"); passwords.push_back(std::make_unique<PasswordForm>(form)); @@ -61,11 +61,11 @@ TEST(PasswordCSVWriterTest, SerializePasswords_SinglePassword) { TEST(PasswordCSVWriterTest, SerializePasswords_TwoPasswords) { std::vector<std::unique_ptr<PasswordForm>> passwords; PasswordForm form; - form.origin = GURL("http://example.com"); + form.url = GURL("http://example.com"); form.username_value = base::UTF8ToUTF16("Someone"); form.password_value = base::UTF8ToUTF16("Secret"); passwords.push_back(std::make_unique<PasswordForm>(form)); - form.origin = GURL("http://other.org"); + form.url = GURL("http://other.org"); form.username_value = base::UTF8ToUTF16("Anyone"); form.password_value = base::UTF8ToUTF16("None"); passwords.push_back(std::make_unique<PasswordForm>(form)); diff --git a/chromium/components/password_manager/core/browser/export/password_manager_exporter.cc b/chromium/components/password_manager/core/browser/export/password_manager_exporter.cc index 1505306fefa..9ef9a0a42d7 100644 --- a/chromium/components/password_manager/core/browser/export/password_manager_exporter.cc +++ b/chromium/components/password_manager/core/browser/export/password_manager_exporter.cc @@ -51,6 +51,10 @@ bool DefaultWriteFunction(const base::FilePath& file, base::StringPiece data) { return base::WriteFile(file, data); } +bool DefaultDeleteFunction(const base::FilePath& file) { + return base::DeleteFile(file, /*recursive=*/false); +} + } // namespace namespace password_manager { @@ -63,7 +67,7 @@ PasswordManagerExporter::PasswordManagerExporter( on_progress_(std::move(on_progress)), last_progress_status_(ExportProgressStatus::NOT_STARTED), write_function_(base::BindRepeating(&DefaultWriteFunction)), - delete_function_(base::BindRepeating(&base::DeleteFile)), + delete_function_(base::BindRepeating(&DefaultDeleteFunction)), #if defined(OS_POSIX) set_permissions_function_( base::BindRepeating(base::SetPosixFilePermissions)), @@ -74,7 +78,7 @@ PasswordManagerExporter::PasswordManagerExporter( task_runner_(g_task_runner.Get()) { } -PasswordManagerExporter::~PasswordManagerExporter() {} +PasswordManagerExporter::~PasswordManagerExporter() = default; void PasswordManagerExporter::PreparePasswordsForExport() { DCHECK_EQ(GetProgressStatus(), ExportProgressStatus::NOT_STARTED); @@ -191,9 +195,9 @@ void PasswordManagerExporter::Cleanup() { // TODO(crbug.com/811779) When Chrome is overwriting an existing file, cancel // should restore the file rather than delete it. if (!destination_.empty()) { - task_runner_->PostTask(FROM_HERE, - base::BindOnce(base::IgnoreResult(delete_function_), - destination_, false)); + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(base::IgnoreResult(delete_function_), destination_)); } } diff --git a/chromium/components/password_manager/core/browser/export/password_manager_exporter.h b/chromium/components/password_manager/core/browser/export/password_manager_exporter.h index fe6c4d5c0a3..1c353b6a5dc 100644 --- a/chromium/components/password_manager/core/browser/export/password_manager_exporter.h +++ b/chromium/components/password_manager/core/browser/export/password_manager_exporter.h @@ -32,8 +32,7 @@ class PasswordManagerExporter { const std::string&)>; using WriteCallback = base::RepeatingCallback<bool(const base::FilePath&, base::StringPiece)>; - using DeleteCallback = - base::RepeatingCallback<bool(const base::FilePath&, bool)>; + using DeleteCallback = base::RepeatingCallback<bool(const base::FilePath&)>; using SetPosixFilePermissionsCallback = base::RepeatingCallback<bool(const base::FilePath&, int)>; diff --git a/chromium/components/password_manager/core/browser/export/password_manager_exporter_unittest.cc b/chromium/components/password_manager/core/browser/export/password_manager_exporter_unittest.cc index d72097d3c2b..2772efc471a 100644 --- a/chromium/components/password_manager/core/browser/export/password_manager_exporter_unittest.cc +++ b/chromium/components/password_manager/core/browser/export/password_manager_exporter_unittest.cc @@ -53,7 +53,7 @@ const base::FilePath::CharType kNullFileName[] = FILE_PATH_LITERAL("/dev/null"); class FakeCredentialProvider : public password_manager::CredentialProviderInterface { public: - FakeCredentialProvider() {} + FakeCredentialProvider() = default; void SetPasswordList( const std::vector<std::unique_ptr<autofill::PasswordForm>>& @@ -83,7 +83,7 @@ class FakeCredentialProvider // Creates a hardcoded set of credentials for tests. std::vector<std::unique_ptr<autofill::PasswordForm>> CreatePasswordList() { auto password_form = std::make_unique<autofill::PasswordForm>(); - password_form->origin = GURL("http://accounts.google.com/a/LoginAuth"); + password_form->url = GURL("http://accounts.google.com/a/LoginAuth"); password_form->username_value = base::ASCIIToUTF16("test@gmail.com"); password_form->password_value = base::ASCIIToUTF16("test1"); @@ -153,7 +153,7 @@ TEST_F(PasswordManagerExporterTest, WriteFileFailed) { destination_path_.DirName().BaseName().AsUTF8Unsafe()); EXPECT_CALL(mock_write_file_, Run(_, _)).WillOnce(Return(false)); - EXPECT_CALL(mock_delete_file_, Run(destination_path_, false)); + EXPECT_CALL(mock_delete_file_, Run(destination_path_)); EXPECT_CALL( mock_on_progress_, Run(password_manager::ExportProgressStatus::IN_PROGRESS, IsEmpty())); @@ -216,7 +216,7 @@ TEST_F(PasswordManagerExporterTest, CancelAfterPasswords) { TEST_F(PasswordManagerExporterTest, CancelWhileExporting) { EXPECT_CALL(mock_write_file_, Run(_, _)).Times(0); - EXPECT_CALL(mock_delete_file_, Run(destination_path_, false)); + EXPECT_CALL(mock_delete_file_, Run(destination_path_)); EXPECT_CALL( mock_on_progress_, Run(password_manager::ExportProgressStatus::IN_PROGRESS, IsEmpty())); @@ -235,7 +235,7 @@ TEST_F(PasswordManagerExporterTest, CancelWhileExporting) { // exporting. If they choose to cancel, we should clear the file. TEST_F(PasswordManagerExporterTest, CancelAfterExporting) { EXPECT_CALL(mock_write_file_, Run(_, _)).WillOnce(Return(true)); - EXPECT_CALL(mock_delete_file_, Run(destination_path_, false)); + EXPECT_CALL(mock_delete_file_, Run(destination_path_)); EXPECT_CALL( mock_on_progress_, Run(password_manager::ExportProgressStatus::IN_PROGRESS, IsEmpty())); diff --git a/chromium/components/password_manager/core/browser/form_fetcher_impl.cc b/chromium/components/password_manager/core/browser/form_fetcher_impl.cc index 6fd1ff1aeb8..bffda9be769 100644 --- a/chromium/components/password_manager/core/browser/form_fetcher_impl.cc +++ b/chromium/components/password_manager/core/browser/form_fetcher_impl.cc @@ -92,8 +92,8 @@ void FormFetcherImpl::RemoveConsumer(FormFetcher::Consumer* consumer) { void FormFetcherImpl::Fetch() { std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogMessage(Logger::STRING_FETCH_METHOD); logger->LogNumber(Logger::STRING_FORM_FETCHER_STATE, static_cast<int>(state_)); @@ -120,7 +120,7 @@ void FormFetcherImpl::Fetch() { // processor cycles. #if !defined(OS_IOS) && !defined(OS_ANDROID) // The statistics is needed for the "Save password?" bubble. - password_store->GetSiteStats(form_digest_.origin.GetOrigin(), this); + password_store->GetSiteStats(form_digest_.url.GetOrigin(), this); // The desktop bubble needs this information. password_store->GetMatchingCompromisedCredentials(form_digest_.signon_realm, @@ -249,16 +249,16 @@ void FormFetcherImpl::OnGetPasswordStoreResults( std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogMessage(Logger::STRING_ON_GET_STORE_RESULTS_METHOD); logger->LogNumber(Logger::STRING_NUMBER_RESULTS, results.size()); } if (should_migrate_http_passwords_ && results.empty() && - form_digest_.origin.SchemeIs(url::kHttpsScheme)) { + form_digest_.url.SchemeIs(url::kHttpsScheme)) { http_migrator_ = std::make_unique<HttpPasswordStoreMigrator>( - form_digest_.origin, client_, this); + url::Origin::Create(form_digest_.url), client_, this); return; } diff --git a/chromium/components/password_manager/core/browser/form_fetcher_impl.h b/chromium/components/password_manager/core/browser/form_fetcher_impl.h index 9ff24e7423a..dd98d6d08ae 100644 --- a/chromium/components/password_manager/core/browser/form_fetcher_impl.h +++ b/chromium/components/password_manager/core/browser/form_fetcher_impl.h @@ -98,6 +98,12 @@ class FormFetcherImpl : public FormFetcher, // non-federated matches. std::vector<std::unique_ptr<autofill::PasswordForm>> federated_; + // Indicates whether HTTP passwords should be migrated to HTTPS. + const bool should_migrate_http_passwords_; + + // Does the actual migration. + std::unique_ptr<HttpPasswordStoreMigrator> http_migrator_; + private: // PasswordStoreConsumer: void OnGetPasswordStoreResults( @@ -139,12 +145,6 @@ class FormFetcherImpl : public FormFetcher, // remove themselves from the list during their destruction. base::ObserverList<FormFetcher::Consumer> consumers_; - // Indicates whether HTTP passwords should be migrated to HTTPS. - const bool should_migrate_http_passwords_; - - // Does the actual migration. - std::unique_ptr<HttpPasswordStoreMigrator> http_migrator_; - DISALLOW_COPY_AND_ASSIGN(FormFetcherImpl); }; diff --git a/chromium/components/password_manager/core/browser/form_fetcher_impl_unittest.cc b/chromium/components/password_manager/core/browser/form_fetcher_impl_unittest.cc index 9834cea409d..eab4a914be8 100644 --- a/chromium/components/password_manager/core/browser/form_fetcher_impl_unittest.cc +++ b/chromium/components/password_manager/core/browser/form_fetcher_impl_unittest.cc @@ -123,7 +123,7 @@ PasswordForm CreateHTMLForm(const char* origin_url, const char* password_value) { PasswordForm form; form.scheme = PasswordForm::Scheme::kHtml; - form.origin = GURL(origin_url); + form.url = GURL(origin_url); form.signon_realm = origin_url; form.username_value = ASCIIToUTF16(username_value); form.password_value = ASCIIToUTF16(password_value); @@ -257,8 +257,8 @@ TEST_P(FormFetcherImplTest, Empty) { Fetch(); form_fetcher_->AddConsumer(&consumer_); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + store_consumer()->OnGetPasswordStoreResultsFrom( + mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), IsEmpty()); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty()); @@ -273,7 +273,8 @@ TEST_P(FormFetcherImplTest, NonFederated) { std::vector<std::unique_ptr<PasswordForm>> results; results.push_back(std::make_unique<PasswordForm>(non_federated)); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(std::move(results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(results)); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(non_federated))); @@ -291,7 +292,8 @@ TEST_P(FormFetcherImplTest, Federated) { results.push_back(std::make_unique<PasswordForm>(federated)); results.push_back(std::make_unique<PasswordForm>(android_federated)); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(std::move(results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(results)); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), IsEmpty()); EXPECT_THAT( @@ -312,7 +314,8 @@ TEST_P(FormFetcherImplTest, Blacklited) { results.push_back(std::make_unique<PasswordForm>(blacklisted)); results.push_back(std::make_unique<PasswordForm>(blacklisted_psl)); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(std::move(results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(results)); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), IsEmpty()); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty()); @@ -346,7 +349,8 @@ TEST_P(FormFetcherImplTest, Mixed) { results.push_back(std::make_unique<PasswordForm>(non_federated3)); results.push_back(std::make_unique<PasswordForm>(blacklisted)); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(std::move(results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(results)); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); EXPECT_THAT( form_fetcher_->GetNonFederatedMatches(), @@ -377,7 +381,8 @@ TEST_P(FormFetcherImplTest, Filtered) { results.push_back(std::make_unique<PasswordForm>(non_federated1)); results.push_back(std::make_unique<PasswordForm>(non_federated2)); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(std::move(results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(results)); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); // Expect that nothing got filtered out, since CredentialsFilter no longer // filters things out: @@ -432,7 +437,8 @@ TEST_P(FormFetcherImplTest, Update_Reentrance) { // Delivering the first results will trigger the new GetLogins call, because // of the Fetch() above. EXPECT_CALL(*mock_store_, GetLogins(form_digest_, form_fetcher_.get())); - store_consumer()->OnGetPasswordStoreResults(std::move(old_results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(old_results)); // Second response from the store should not be ignored. PasswordForm form_b = CreateNonFederated(); @@ -445,7 +451,8 @@ TEST_P(FormFetcherImplTest, Update_Reentrance) { std::vector<std::unique_ptr<PasswordForm>> results; results.push_back(std::make_unique<PasswordForm>(form_b)); results.push_back(std::make_unique<PasswordForm>(form_c)); - store_consumer()->OnGetPasswordStoreResults(std::move(results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(results)); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(form_b), Pointee(form_c))); } @@ -453,7 +460,7 @@ TEST_P(FormFetcherImplTest, Update_Reentrance) { #if !defined(OS_IOS) && !defined(OS_ANDROID) TEST_P(FormFetcherImplTest, FetchStatistics) { InteractionsStats stats; - stats.origin_domain = form_digest_.origin.GetOrigin(); + stats.origin_domain = form_digest_.url.GetOrigin(); stats.username_value = ASCIIToUTF16("some username"); stats.dismissal_count = 5; std::vector<InteractionsStats> db_stats = {stats}; @@ -500,9 +507,9 @@ TEST_P(FormFetcherImplTest, DontFetchCompromised) { TEST_P(FormFetcherImplTest, DoNotTryToMigrateHTTPPasswordsOnHTTPSites) { GURL::Replacements http_rep; http_rep.SetSchemeStr(url::kHttpScheme); - const GURL http_origin = form_digest_.origin.ReplaceComponents(http_rep); + const GURL http_url = form_digest_.url.ReplaceComponents(http_rep); form_digest_ = PasswordStore::FormDigest( - PasswordForm::Scheme::kHtml, http_origin.GetOrigin().spec(), http_origin); + PasswordForm::Scheme::kHtml, http_url.GetOrigin().spec(), http_url); // A new form fetcher is created to be able to set the form digest and // migration flag. @@ -519,14 +526,16 @@ TEST_P(FormFetcherImplTest, DoNotTryToMigrateHTTPPasswordsOnHTTPSites) { EXPECT_CALL(*mock_store_, GetLogins(_, _)).Times(0); EXPECT_CALL(*mock_store_, AddLogin(_)).Times(0); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(MakeResults(empty_forms)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + MakeResults(empty_forms)); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), IsEmpty()); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty()); EXPECT_FALSE(form_fetcher_->IsBlacklisted()); Fetch(); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(MakeResults({http_form})); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + MakeResults({http_form})); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(http_form))); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty()); @@ -534,8 +543,8 @@ TEST_P(FormFetcherImplTest, DoNotTryToMigrateHTTPPasswordsOnHTTPSites) { Fetch(); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults( - MakeResults({http_form, federated_form})); + store_consumer()->OnGetPasswordStoreResultsFrom( + mock_store_, MakeResults({http_form, federated_form})); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(http_form))); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), @@ -548,10 +557,9 @@ TEST_P(FormFetcherImplTest, DoNotTryToMigrateHTTPPasswordsOnHTTPSites) { TEST_P(FormFetcherImplTest, TryToMigrateHTTPPasswordsOnHTTPSSites) { GURL::Replacements https_rep; https_rep.SetSchemeStr(url::kHttpsScheme); - const GURL https_origin = form_digest_.origin.ReplaceComponents(https_rep); - form_digest_ = - PasswordStore::FormDigest(PasswordForm::Scheme::kHtml, - https_origin.GetOrigin().spec(), https_origin); + const GURL https_url = form_digest_.url.ReplaceComponents(https_rep); + form_digest_ = PasswordStore::FormDigest( + PasswordForm::Scheme::kHtml, https_url.GetOrigin().spec(), https_url); // A new form fetcher is created to be able to set the form digest and // migration flag. @@ -567,30 +575,31 @@ TEST_P(FormFetcherImplTest, TryToMigrateHTTPPasswordsOnHTTPSSites) { GURL::Replacements http_rep; http_rep.SetSchemeStr(url::kHttpScheme); PasswordForm http_form = https_form; - http_form.origin = https_form.origin.ReplaceComponents(http_rep); - http_form.signon_realm = http_form.origin.GetOrigin().spec(); + http_form.url = https_form.url.ReplaceComponents(http_rep); + http_form.signon_realm = http_form.url.GetOrigin().spec(); std::vector<PasswordForm> empty_forms; // Tests that there is only an attempt to migrate credentials on HTTPS origins // when no other credentials are available. - const GURL form_digest_http_origin = - form_digest_.origin.ReplaceComponents(http_rep); + const GURL form_digest_http_url = + form_digest_.url.ReplaceComponents(http_rep); PasswordStore::FormDigest http_form_digest( - PasswordForm::Scheme::kHtml, form_digest_http_origin.GetOrigin().spec(), - form_digest_http_origin); + PasswordForm::Scheme::kHtml, form_digest_http_url.GetOrigin().spec(), + form_digest_http_url); Fetch(); base::WeakPtr<PasswordStoreConsumer> migrator_ptr; EXPECT_CALL(*mock_store_, GetLogins(http_form_digest, _)) .WillOnce(WithArg<1>(GetAndAssignWeakPtr(&migrator_ptr))); - store_consumer()->OnGetPasswordStoreResults(MakeResults(empty_forms)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + MakeResults(empty_forms)); ASSERT_TRUE(migrator_ptr); // Now perform the actual migration. EXPECT_CALL(*mock_store_, AddLogin(https_form)); EXPECT_CALL(consumer_, OnFetchCompleted); static_cast<HttpPasswordStoreMigrator*>(migrator_ptr.get()) - ->OnGetPasswordStoreResults(MakeResults({http_form})); + ->OnGetPasswordStoreResultsFrom(mock_store_, MakeResults({http_form})); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(https_form))); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty()); @@ -601,7 +610,8 @@ TEST_P(FormFetcherImplTest, TryToMigrateHTTPPasswordsOnHTTPSSites) { EXPECT_CALL(*mock_store_, GetLogins(_, _)).Times(0); EXPECT_CALL(*mock_store_, AddLogin(_)).Times(0); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults(MakeResults({https_form})); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + MakeResults({https_form})); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(https_form))); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty()); @@ -610,8 +620,8 @@ TEST_P(FormFetcherImplTest, TryToMigrateHTTPPasswordsOnHTTPSSites) { const PasswordForm federated_form = CreateFederated(); Fetch(); EXPECT_CALL(consumer_, OnFetchCompleted); - store_consumer()->OnGetPasswordStoreResults( - MakeResults({https_form, federated_form})); + store_consumer()->OnGetPasswordStoreResultsFrom( + mock_store_, MakeResults({https_form, federated_form})); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(https_form))); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), @@ -624,10 +634,9 @@ TEST_P(FormFetcherImplTest, TryToMigrateHTTPPasswordsOnHTTPSSites) { TEST_P(FormFetcherImplTest, StateIsWaitingDuringMigration) { GURL::Replacements https_rep; https_rep.SetSchemeStr(url::kHttpsScheme); - const GURL https_origin = form_digest_.origin.ReplaceComponents(https_rep); - form_digest_ = - PasswordStore::FormDigest(PasswordForm::Scheme::kHtml, - https_origin.GetOrigin().spec(), https_origin); + const GURL https_url = form_digest_.url.ReplaceComponents(https_rep); + form_digest_ = PasswordStore::FormDigest( + PasswordForm::Scheme::kHtml, https_url.GetOrigin().spec(), https_url); // A new form fetcher is created to be able to set the form digest and // migration flag. @@ -641,18 +650,18 @@ TEST_P(FormFetcherImplTest, StateIsWaitingDuringMigration) { GURL::Replacements http_rep; http_rep.SetSchemeStr(url::kHttpScheme); PasswordForm http_form = https_form; - http_form.origin = https_form.origin.ReplaceComponents(http_rep); - http_form.signon_realm = http_form.origin.GetOrigin().spec(); + http_form.url = https_form.url.ReplaceComponents(http_rep); + http_form.signon_realm = http_form.url.GetOrigin().spec(); std::vector<PasswordForm> empty_forms; // Ensure there is an attempt to migrate credentials on HTTPS origins and // extract the migrator. - const GURL form_digest_http_origin = - form_digest_.origin.ReplaceComponents(http_rep); + const GURL form_digest_http_url = + form_digest_.url.ReplaceComponents(http_rep); PasswordStore::FormDigest http_form_digest( - PasswordForm::Scheme::kHtml, form_digest_http_origin.GetOrigin().spec(), - form_digest_http_origin); + PasswordForm::Scheme::kHtml, form_digest_http_url.GetOrigin().spec(), + form_digest_http_url); Fetch(); // First the FormFetcher is waiting for the initial response from // PasswordStore. @@ -660,7 +669,8 @@ TEST_P(FormFetcherImplTest, StateIsWaitingDuringMigration) { base::WeakPtr<PasswordStoreConsumer> migrator_ptr; EXPECT_CALL(*mock_store_, GetLogins(http_form_digest, _)) .WillOnce(WithArg<1>(GetAndAssignWeakPtr(&migrator_ptr))); - store_consumer()->OnGetPasswordStoreResults(MakeResults(empty_forms)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + MakeResults(empty_forms)); ASSERT_TRUE(migrator_ptr); // While the initial results from PasswordStore arrived to the FormFetcher, it // should be still waiting for the migrator. @@ -669,7 +679,7 @@ TEST_P(FormFetcherImplTest, StateIsWaitingDuringMigration) { // Now perform the actual migration. EXPECT_CALL(*mock_store_, AddLogin(https_form)); static_cast<HttpPasswordStoreMigrator*>(migrator_ptr.get()) - ->OnGetPasswordStoreResults(MakeResults({http_form})); + ->OnGetPasswordStoreResultsFrom(mock_store_, MakeResults({http_form})); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); } @@ -677,8 +687,8 @@ TEST_P(FormFetcherImplTest, StateIsWaitingDuringMigration) { // instance with empty results. TEST_P(FormFetcherImplTest, Clone_EmptyResults) { Fetch(); - store_consumer()->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + store_consumer()->OnGetPasswordStoreResultsFrom( + mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(mock_store_.get())); // Clone() should not cause re-fetching from PasswordStore. @@ -705,7 +715,8 @@ TEST_P(FormFetcherImplTest, Clone_NonEmptyResults) { results.push_back(std::make_unique<PasswordForm>(federated)); results.push_back(std::make_unique<PasswordForm>(android_federated)); - store_consumer()->OnGetPasswordStoreResults(std::move(results)); + store_consumer()->OnGetPasswordStoreResultsFrom(mock_store_, + std::move(results)); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), UnorderedElementsAre(Pointee(non_federated))); EXPECT_THAT( @@ -740,8 +751,8 @@ TEST_P(FormFetcherImplTest, Clone_NonEmptyResults) { TEST_P(FormFetcherImplTest, Clone_Stats) { Fetch(); // Pass empty results to make the state NOT_WAITING. - store_consumer()->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + store_consumer()->OnGetPasswordStoreResultsFrom( + mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); std::vector<InteractionsStats> stats(1); store_consumer()->OnGetSiteStatistics(std::move(stats)); @@ -752,8 +763,8 @@ TEST_P(FormFetcherImplTest, Clone_Stats) { TEST_P(FormFetcherImplTest, Clone_Compromised) { Fetch(); // Pass empty results to make the state NOT_WAITING. - store_consumer()->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + store_consumer()->OnGetPasswordStoreResultsFrom( + mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); const std::vector<CompromisedCredentials> credentials = { {form_digest_.signon_realm, base::ASCIIToUTF16("username_value"), base::Time::FromTimeT(1), CompromiseType::kLeaked}}; @@ -771,8 +782,8 @@ TEST_P(FormFetcherImplTest, RemoveConsumer) { form_fetcher_->AddConsumer(&consumer_); form_fetcher_->RemoveConsumer(&consumer_); EXPECT_CALL(consumer_, OnFetchCompleted).Times(0); - store_consumer()->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + store_consumer()->OnGetPasswordStoreResultsFrom( + mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); } // Check that destroying the fetcher while notifying its consumers is handled @@ -795,7 +806,8 @@ TEST_P(FormFetcherImplTest, DestroyFetcherFromConsumer) { EXPECT_CALL(consumer_, OnFetchCompleted).Times(0); static_cast<PasswordStoreConsumer*>(form_fetcher) - ->OnGetPasswordStoreResults(std::vector<std::unique_ptr<PasswordForm>>()); + ->OnGetPasswordStoreResultsFrom( + mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); } INSTANTIATE_TEST_SUITE_P(All, diff --git a/chromium/components/password_manager/core/browser/form_parsing/form_parser.cc b/chromium/components/password_manager/core/browser/form_parsing/form_parser.cc index 04b67f3d888..5e27c849a28 100644 --- a/chromium/components/password_manager/core/browser/form_parsing/form_parser.cc +++ b/chromium/components/password_manager/core/browser/form_parsing/form_parser.cc @@ -13,7 +13,7 @@ #include <utility> #include <vector> -#include "base/metrics/histogram_macros.h" +#include "base/metrics/histogram_functions.h" #include "base/no_destructor.h" #include "base/stl_util.h" #include "base/strings/string16.h" @@ -976,7 +976,7 @@ std::unique_ptr<PasswordForm> AssemblePasswordForm( // Create the PasswordForm and set data not related to specific fields. auto result = std::make_unique<PasswordForm>(); - result->origin = form_data.url; + result->url = form_data.url; result->signon_realm = GetSignonRealm(form_data.url); result->action = form_data.action; result->form_data = form_data; @@ -1027,27 +1027,23 @@ std::unique_ptr<PasswordForm> FormDataParser::Parse(const FormData& form_data, return nullptr; SignificantFields significant_fields; - UsernameDetectionMethod username_detection_method = - UsernameDetectionMethod::kNoUsernameDetected; + UsernameDetectionMethod method = UsernameDetectionMethod::kNoUsernameDetected; // (1) First, try to parse with server predictions. if (predictions_) { ParseUsingPredictions(&processed_fields, *predictions_, mode, &significant_fields); if (significant_fields.username) { - username_detection_method = - UsernameDetectionMethod::kServerSidePrediction; + method = UsernameDetectionMethod::kServerSidePrediction; } } // (2) If that failed, try to parse with autocomplete attributes. if (!significant_fields.is_single_username) { ParseUsingAutocomplete(processed_fields, &significant_fields); - if (username_detection_method == - UsernameDetectionMethod::kNoUsernameDetected && + if (method == UsernameDetectionMethod::kNoUsernameDetected && significant_fields.username) { - username_detection_method = - UsernameDetectionMethod::kAutocompleteAttribute; + method = UsernameDetectionMethod::kAutocompleteAttribute; } } @@ -1067,10 +1063,9 @@ std::unique_ptr<PasswordForm> FormDataParser::Parse(const FormData& form_data, Interactability username_max = Interactability::kUnlikely; ParseUsingBaseHeuristics(processed_fields, mode, &significant_fields, &username_max, &readonly_status_); - if (username_detection_method == - UsernameDetectionMethod::kNoUsernameDetected && + if (method == UsernameDetectionMethod::kNoUsernameDetected && significant_fields.username) { - username_detection_method = UsernameDetectionMethod::kBaseHeuristic; + method = UsernameDetectionMethod::kBaseHeuristic; } // Additionally, and based on the best interactability computed by base @@ -1085,20 +1080,16 @@ std::unique_ptr<PasswordForm> FormDataParser::Parse(const FormData& form_data, !(mode == FormDataParser::Mode::kSaving && username_field_by_context->value.empty())) { significant_fields.username = username_field_by_context; - if (username_detection_method == - UsernameDetectionMethod::kNoUsernameDetected || - username_detection_method == - UsernameDetectionMethod::kBaseHeuristic) { - username_detection_method = - UsernameDetectionMethod::kHtmlBasedClassifier; + if (method == UsernameDetectionMethod::kNoUsernameDetected || + method == UsernameDetectionMethod::kBaseHeuristic) { + method = UsernameDetectionMethod::kHtmlBasedClassifier; } } } } - UMA_HISTOGRAM_ENUMERATION("PasswordManager.UsernameDetectionMethod", - username_detection_method, - UsernameDetectionMethod::kCount); + base::UmaHistogramEnumeration("PasswordManager.UsernameDetectionMethod", + method); return AssemblePasswordForm(form_data, significant_fields, std::move(all_possible_passwords), diff --git a/chromium/components/password_manager/core/browser/form_parsing/form_parser.h b/chromium/components/password_manager/core/browser/form_parsing/form_parser.h index 7adcace01ab..a69e1448029 100644 --- a/chromium/components/password_manager/core/browser/form_parsing/form_parser.h +++ b/chromium/components/password_manager/core/browser/form_parsing/form_parser.h @@ -39,7 +39,7 @@ class FormDataParser { kHtmlBasedClassifier = 2, kAutocompleteAttribute = 3, kServerSidePrediction = 4, - kCount + kMaxValue = kServerSidePrediction, }; // Records whether password fields with a "readonly" attribute were ignored diff --git a/chromium/components/password_manager/core/browser/form_saver.h b/chromium/components/password_manager/core/browser/form_saver.h index b730e06d932..494abaf845c 100644 --- a/chromium/components/password_manager/core/browser/form_saver.h +++ b/chromium/components/password_manager/core/browser/form_saver.h @@ -53,10 +53,10 @@ class FormSaver { const std::vector<const autofill::PasswordForm*>& matches, const base::string16& old_password) = 0; - // If any of the primary key fields (signon_realm, origin, username_element, + // If any of the unique key fields (signon_realm, origin, username_element, // username_value, password_element) are updated, then the this version of - // the Update method must be used, which takes |old_primary_key|, i.e., the - // old values for the primary key fields (the rest of the fields are ignored). + // the Update method must be used, which takes |old_unique_key|, i.e., the + // old values for the unique key fields (the rest of the fields are ignored). // The algorithm for handling |matches| and |old_password| is the same as // above. virtual void UpdateReplace( diff --git a/chromium/components/password_manager/core/browser/form_saver_impl_unittest.cc b/chromium/components/password_manager/core/browser/form_saver_impl_unittest.cc index d684f2d74ec..a8550278cbf 100644 --- a/chromium/components/password_manager/core/browser/form_saver_impl_unittest.cc +++ b/chromium/components/password_manager/core/browser/form_saver_impl_unittest.cc @@ -37,8 +37,8 @@ namespace { // Creates a dummy observed form with some basic arbitrary values. PasswordForm CreateObserved() { PasswordForm form; - form.origin = GURL("https://example.in"); - form.signon_realm = form.origin.spec(); + form.url = GURL("https://example.in"); + form.signon_realm = form.url.spec(); form.action = GURL("https://login.example.org"); return form; } @@ -218,7 +218,7 @@ TEST_P(FormSaverImplSaveTest, Write_AndUpdatePasswordValuesOnExactMatch) { constexpr char kNewPassword[] = "new_password"; PasswordForm duplicate = CreatePending("nameofuser", kOldPassword); - duplicate.origin = GURL("https://example.in/somePath"); + duplicate.url = GURL("https://example.in/somePath"); PasswordForm expected_update = duplicate; expected_update.password_value = ASCIIToUTF16(kNewPassword); @@ -234,8 +234,8 @@ TEST_P(FormSaverImplSaveTest, Write_AndUpdatePasswordValuesOnPSLMatch) { constexpr char kNewPassword[] = "new_password"; PasswordForm duplicate = CreatePending("nameofuser", kOldPassword); - duplicate.origin = GURL("https://www.example.in"); - duplicate.signon_realm = duplicate.origin.spec(); + duplicate.url = GURL("https://www.example.in"); + duplicate.signon_realm = duplicate.url.spec(); duplicate.is_public_suffix_match = true; PasswordForm expected_update = duplicate; @@ -328,7 +328,7 @@ TEST_F(FormSaverImplTest, PermanentlyBlacklist) { observed.password_element = ASCIIToUTF16("password"); observed.all_possible_usernames = { {ASCIIToUTF16("user2"), ASCIIToUTF16("field")}}; - observed.origin = GURL("https://www.example.com/foobar"); + observed.url = GURL("https://www.example.com/foobar"); PasswordForm blacklisted = password_manager_util::MakeNormalizedBlacklistedForm( diff --git a/chromium/components/password_manager/core/browser/generation/password_requirements_spec_fetcher_impl.cc b/chromium/components/password_manager/core/browser/generation/password_requirements_spec_fetcher_impl.cc index bccf860fa92..bf18dee292d 100644 --- a/chromium/components/password_manager/core/browser/generation/password_requirements_spec_fetcher_impl.cc +++ b/chromium/components/password_manager/core/browser/generation/password_requirements_spec_fetcher_impl.cc @@ -65,7 +65,7 @@ std::string GetHashPrefix(const GURL& origin, size_t prefix_length) { base::MD5Digest digest; base::MD5Sum(domain_and_registry.data(), domain_and_registry.size(), &digest); - for (size_t i = 0; i < base::size(digest.a); ++i) { + for (auto& byte : digest.a) { if (prefix_length >= 8) { prefix_length -= 8; continue; @@ -73,7 +73,7 @@ std::string GetHashPrefix(const GURL& origin, size_t prefix_length) { // Determine the |prefix_length| most significant bits by calculating // the 8 - |prefix_length| least significant bits and inverting the // result. - digest.a[i] &= ~((1 << (8 - prefix_length)) - 1); + byte &= ~((1 << (8 - prefix_length)) - 1); prefix_length = 0; } } @@ -137,15 +137,14 @@ void PasswordRequirementsSpecFetcherImpl::Fetch(GURL origin, // If a lookup is happening already, just register another callback. auto iter = lookups_in_flight_.find(hash_prefix); if (iter != lookups_in_flight_.end()) { - iter->second->callbacks.push_back( - std::make_pair(origin, std::move(callback))); + iter->second->callbacks.emplace_back(origin, std::move(callback)); VLOG(1) << "Lookup already in flight"; return; } // Start another lookup otherwise. auto lookup = std::make_unique<LookupInFlight>(); - lookup->callbacks.push_back(std::make_pair(origin, std::move(callback))); + lookup->callbacks.emplace_back(origin, std::move(callback)); lookup->start_of_request = base::TimeTicks::Now(); net::NetworkTrafficAnnotationTag traffic_annotation = diff --git a/chromium/components/password_manager/core/browser/hsts_query.cc b/chromium/components/password_manager/core/browser/hsts_query.cc index dfdf900b962..213ccd1f923 100644 --- a/chromium/components/password_manager/core/browser/hsts_query.cc +++ b/chromium/components/password_manager/core/browser/hsts_query.cc @@ -18,7 +18,7 @@ namespace { // Helper since a once-callback may need to be called from two paths. class HSTSCallbackHelper : public base::RefCounted<HSTSCallbackHelper> { public: - HSTSCallbackHelper(HSTSCallback user_callback) + explicit HSTSCallbackHelper(HSTSCallback user_callback) : user_callback_(std::move(user_callback)) {} void ReportResult(bool result) { @@ -39,10 +39,10 @@ class HSTSCallbackHelper : public base::RefCounted<HSTSCallbackHelper> { } // namespace void PostHSTSQueryForHostAndNetworkContext( - const GURL& origin, + const url::Origin& origin, network::mojom::NetworkContext* network_context, HSTSCallback callback) { - if (!origin.is_valid()) { + if (origin.opaque()) { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), HSTSResult::kNo)); return; diff --git a/chromium/components/password_manager/core/browser/hsts_query.h b/chromium/components/password_manager/core/browser/hsts_query.h index 6bf21cce34d..d92ef83990e 100644 --- a/chromium/components/password_manager/core/browser/hsts_query.h +++ b/chromium/components/password_manager/core/browser/hsts_query.h @@ -7,14 +7,16 @@ #include "base/callback_forward.h" -class GURL; - namespace network { namespace mojom { class NetworkContext; } } // namespace network +namespace url { +class Origin; +} + namespace password_manager { enum class HSTSResult { kNo, kYes, kError }; @@ -27,7 +29,7 @@ using HSTSCallback = base::OnceCallback<void(HSTSResult)>; // thread the network context lives on (in things based on content/ the UI // thread). void PostHSTSQueryForHostAndNetworkContext( - const GURL& origin, + const url::Origin& origin, network::mojom::NetworkContext* network_context, HSTSCallback callback); diff --git a/chromium/components/password_manager/core/browser/hsts_query_unittest.cc b/chromium/components/password_manager/core/browser/hsts_query_unittest.cc index 98a9b115708..b4707b61d18 100644 --- a/chromium/components/password_manager/core/browser/hsts_query_unittest.cc +++ b/chromium/components/password_manager/core/browser/hsts_query_unittest.cc @@ -81,7 +81,7 @@ class HSTSQueryTest : public testing::Test { }; TEST_F(HSTSQueryTest, TestPostHSTSQueryForHostAndRequestContext) { - const GURL origin("https://example.org"); + const url::Origin origin = url::Origin::Create(GURL("https://example.org")); for (bool is_hsts : {false, true}) { SCOPED_TRACE(testing::Message() << std::boolalpha << "is_hsts: " << is_hsts); @@ -107,7 +107,7 @@ TEST_F(HSTSQueryTest, TestPostHSTSQueryForHostAndRequestContext) { } TEST_F(HSTSQueryTest, NullNetworkContext) { - const GURL origin("https://example.org"); + const url::Origin origin = url::Origin::Create(GURL("https://example.org")); bool callback_ran = false; PostHSTSQueryForHostAndNetworkContext( origin, nullptr, diff --git a/chromium/components/password_manager/core/browser/http_auth_manager_impl.cc b/chromium/components/password_manager/core/browser/http_auth_manager_impl.cc index f87ccd7390b..e4cdc054c0c 100644 --- a/chromium/components/password_manager/core/browser/http_auth_manager_impl.cc +++ b/chromium/components/password_manager/core/browser/http_auth_manager_impl.cc @@ -76,7 +76,7 @@ void HttpAuthManagerImpl::Autofill( const PasswordFormManagerForUI* form_manager) const { DCHECK_NE(PasswordForm::Scheme::kHtml, preferred_match.scheme); if (observer_ && (form_manager_.get() == form_manager) && - client_->IsFillingEnabled(form_manager_->GetOrigin())) { + client_->IsFillingEnabled(form_manager_->GetURL())) { observer_->OnAutofillDataAvailable(preferred_match.username_value, preferred_match.password_value); } @@ -84,7 +84,7 @@ void HttpAuthManagerImpl::Autofill( void HttpAuthManagerImpl::OnPasswordFormSubmitted( const PasswordForm& password_form) { - if (client_->IsSavingAndFillingEnabled(password_form.origin)) + if (client_->IsSavingAndFillingEnabled(password_form.url)) ProvisionallySaveForm(password_form); } @@ -119,7 +119,7 @@ void HttpAuthManagerImpl::OnDidFinishMainFrameNavigation() { void HttpAuthManagerImpl::OnLoginSuccesfull() { LogMessage(Logger::STRING_HTTPAUTH_ON_ASK_USER_OR_SAVE_PASSWORD); if (!form_manager_ || - !client_->IsSavingAndFillingEnabled(form_manager_->GetOrigin())) { + !client_->IsSavingAndFillingEnabled(form_manager_->GetURL())) { return; } diff --git a/chromium/components/password_manager/core/browser/http_auth_manager_unittest.cc b/chromium/components/password_manager/core/browser/http_auth_manager_unittest.cc index 6874807e212..8d5817669c1 100644 --- a/chromium/components/password_manager/core/browser/http_auth_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/http_auth_manager_unittest.cc @@ -27,6 +27,7 @@ #include "components/password_manager/core/browser/password_store.h" #include "components/password_manager/core/browser/password_store_consumer.h" #include "components/password_manager/core/browser/stub_password_manager_client.h" +#include "components/password_manager/core/common/password_manager_features.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -49,14 +50,13 @@ namespace { class MockPasswordManagerClient : public StubPasswordManagerClient { public: - MockPasswordManagerClient() {} - MOCK_CONST_METHOD1(IsSavingAndFillingEnabled, bool(const GURL&)); MOCK_CONST_METHOD1(IsFillingEnabled, bool(const GURL&)); MOCK_METHOD2(AutofillHttpAuth, void(const autofill::PasswordForm&, const PasswordFormManagerForUI*)); MOCK_CONST_METHOD0(GetProfilePasswordStore, PasswordStore*()); + MOCK_CONST_METHOD0(GetAccountPasswordStore, PasswordStore*()); MOCK_METHOD0(PromptUserToSaveOrUpdatePasswordPtr, void()); // Workaround for std::unique_ptr<> lacking a copy constructor. @@ -71,7 +71,6 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { class MockHttpAuthObserver : public HttpAuthObserver { public: MockHttpAuthObserver() = default; - ~MockHttpAuthObserver() override = default; MOCK_METHOD0(OnLoginModelDestroying, void()); MOCK_METHOD2(OnAutofillDataAvailable, @@ -80,15 +79,9 @@ class MockHttpAuthObserver : public HttpAuthObserver { DISALLOW_COPY_AND_ASSIGN(MockHttpAuthObserver); }; -// Invokes the password store consumer with a single copy of |form|. -ACTION_P(InvokeConsumer, form) { - std::vector<std::unique_ptr<PasswordForm>> result; - result.push_back(std::make_unique<PasswordForm>(form)); - arg0->OnGetPasswordStoreResults(std::move(result)); -} - -ACTION(InvokeEmptyConsumerWithForms) { - arg0->OnGetPasswordStoreResults(std::vector<std::unique_ptr<PasswordForm>>()); +ACTION_P(InvokeEmptyConsumerWithForms, store) { + arg0->OnGetPasswordStoreResultsFrom( + store, std::vector<std::unique_ptr<PasswordForm>>()); } } // namespace @@ -100,13 +93,31 @@ class HttpAuthManagerTest : public testing::Test { protected: void SetUp() override { store_ = new testing::StrictMock<MockPasswordStore>; - ASSERT_TRUE(store_->Init(nullptr)); + ASSERT_TRUE(store_->Init(/*prefs=*/nullptr)); + + if (base::FeatureList::IsEnabled( + features::kEnablePasswordsAccountStorage)) { + account_store_ = new testing::NiceMock<MockPasswordStore>; + ASSERT_TRUE(account_store_->Init(/*prefs=*/nullptr)); + + // Most tests don't really need the account store, but it'll still get + // queried by MultiStoreFormFetcher, so it needs to return something to + // its consumers. Let the account store return empty results by default, + // so that not every test has to set this up individually. Individual + // tests that do cover the account store can still override this. + ON_CALL(*account_store_, GetLogins(_, _)) + .WillByDefault( + WithArg<1>(InvokeEmptyConsumerWithForms(account_store_.get()))); + } ON_CALL(client_, GetProfilePasswordStore()) .WillByDefault(Return(store_.get())); + ON_CALL(client_, GetAccountPasswordStore()) + .WillByDefault(Return(account_store_.get())); + EXPECT_CALL(*store_, GetSiteStatsImpl(_)).Times(AnyNumber()); - httpauth_manager_.reset(new HttpAuthManagerImpl(&client_)); + httpauth_manager_ = std::make_unique<HttpAuthManagerImpl>(&client_); EXPECT_CALL(*store_, IsAbleToSavePasswords()).WillRepeatedly(Return(true)); @@ -116,6 +127,10 @@ class HttpAuthManagerTest : public testing::Test { } void TearDown() override { + if (account_store_) { + account_store_->ShutdownOnUIThread(); + account_store_ = nullptr; + } store_->ShutdownOnUIThread(); store_ = nullptr; } @@ -124,6 +139,7 @@ class HttpAuthManagerTest : public testing::Test { base::test::TaskEnvironment task_environment_; scoped_refptr<MockPasswordStore> store_; + scoped_refptr<MockPasswordStore> account_store_; testing::NiceMock<MockPasswordManagerClient> client_; std::unique_ptr<HttpAuthManagerImpl> httpauth_manager_; }; @@ -136,7 +152,7 @@ TEST_F(HttpAuthManagerTest, HttpAuthFilling) { PasswordForm observed_form; observed_form.scheme = PasswordForm::Scheme::kBasic; - observed_form.origin = GURL("http://proxy.com/"); + observed_form.url = GURL("http://proxy.com/"); observed_form.signon_realm = "proxy.com/realm"; PasswordForm stored_form = observed_form; @@ -155,7 +171,7 @@ TEST_F(HttpAuthManagerTest, HttpAuthFilling) { ASSERT_TRUE(consumer); std::vector<std::unique_ptr<PasswordForm>> result; result.push_back(std::make_unique<PasswordForm>(stored_form)); - consumer->OnGetPasswordStoreResults(std::move(result)); + consumer->OnGetPasswordStoreResultsFrom(store_, std::move(result)); testing::Mock::VerifyAndClearExpectations(&store_); httpauth_manager()->DetachObserver(&observer); } @@ -170,12 +186,12 @@ TEST_F(HttpAuthManagerTest, HttpAuthSaving) { .WillRepeatedly(Return(filling_and_saving_enabled)); PasswordForm observed_form; observed_form.scheme = PasswordForm::Scheme::kBasic; - observed_form.origin = GURL("http://proxy.com/"); + observed_form.url = GURL("http://proxy.com/"); observed_form.signon_realm = "proxy.com/realm"; MockHttpAuthObserver observer; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); // Initiate creating a form manager. httpauth_manager()->SetObserverAndDeliverCredentials(&observer, @@ -202,12 +218,12 @@ TEST_F(HttpAuthManagerTest, NavigationWithoutSubmission) { .WillRepeatedly(Return(true)); PasswordForm observed_form; observed_form.scheme = PasswordForm::Scheme::kBasic; - observed_form.origin = GURL("http://proxy.com/"); + observed_form.url = GURL("http://proxy.com/"); observed_form.signon_realm = "proxy.com/realm"; MockHttpAuthObserver observer; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); // Initiate creating a form manager. httpauth_manager()->SetObserverAndDeliverCredentials(&observer, @@ -223,7 +239,7 @@ TEST_F(HttpAuthManagerTest, NavigationWhenMatchingNotReady) { EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true)); PasswordForm observed_form; observed_form.scheme = PasswordForm::Scheme::kBasic; - observed_form.origin = GURL("http://proxy.com/"); + observed_form.url = GURL("http://proxy.com/"); observed_form.signon_realm = "proxy.com/realm"; MockHttpAuthObserver observer; diff --git a/chromium/components/password_manager/core/browser/http_credentials_cleaner.cc b/chromium/components/password_manager/core/browser/http_credentials_cleaner.cc index 9146f096076..60e380f863e 100644 --- a/chromium/components/password_manager/core/browser/http_credentials_cleaner.cc +++ b/chromium/components/password_manager/core/browser/http_credentials_cleaner.cc @@ -47,8 +47,8 @@ void HttpCredentialCleaner::OnGetPasswordStoreResults( {std::string( password_manager_util::GetSignonRealmWithProtocolExcluded(*form)), form->scheme, form->username_value}); - if (form->origin.SchemeIs(url::kHttpScheme)) { - const GURL origin = form->origin; + if (form->url.SchemeIs(url::kHttpScheme)) { + auto origin = url::Origin::Create(form->url); PostHSTSQueryForHostAndNetworkContext( origin, network_context_getter_.Run(), base::BindOnce(&HttpCredentialCleaner::OnHSTSQueryResult, diff --git a/chromium/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc b/chromium/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc index a4024a60b67..9f1a1740eb9 100644 --- a/chromium/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc +++ b/chromium/components/password_manager/core/browser/http_credentials_cleaner_unittest.cc @@ -152,7 +152,7 @@ TEST_P(HttpCredentialCleanerTest, ReportHttpMigrationMetrics) { << ", same_password=" << test.same_password); autofill::PasswordForm http_form; - http_form.origin = GURL("http://example.org/"); + http_form.url = GURL("http://example.org/"); http_form.signon_realm = "http://example.org/"; http_form.scheme = test.http_form_scheme; http_form.username_value = username[1]; @@ -160,7 +160,7 @@ TEST_P(HttpCredentialCleanerTest, ReportHttpMigrationMetrics) { store_->AddLogin(http_form); autofill::PasswordForm https_form; - https_form.origin = GURL("https://example.org/"); + https_form.url = GURL("https://example.org/"); https_form.signon_realm = signon_realm[test.same_signon_realm]; https_form.username_value = username[test.same_username]; https_form.password_value = password[test.same_password]; @@ -183,7 +183,7 @@ TEST_P(HttpCredentialCleanerTest, ReportHttpMigrationMetrics) { if (test.is_hsts_enabled) { base::RunLoop run_loop; - network_context->AddHSTS(http_form.origin.host(), base::Time::Max(), + network_context->AddHSTS(http_form.url.host(), base::Time::Max(), false /*include_subdomains*/, run_loop.QuitClosure()); run_loop.Run(); diff --git a/chromium/components/password_manager/core/browser/http_password_store_migrator.cc b/chromium/components/password_manager/core/browser/http_password_store_migrator.cc index 637bc8ce5af..5bc82989b88 100644 --- a/chromium/components/password_manager/core/browser/http_password_store_migrator.cc +++ b/chromium/components/password_manager/core/browser/http_password_store_migrator.cc @@ -37,20 +37,20 @@ void OnHSTSQueryResultHelper( } // namespace HttpPasswordStoreMigrator::HttpPasswordStoreMigrator( - const GURL& https_origin, + const url::Origin& https_origin, const PasswordManagerClient* client, Consumer* consumer) : client_(client), consumer_(consumer) { DCHECK(client_); - DCHECK(https_origin.is_valid()); - DCHECK(https_origin.SchemeIs(url::kHttpsScheme)) << https_origin; + DCHECK(!https_origin.opaque()); + DCHECK_EQ(https_origin.scheme(), url::kHttpsScheme) << https_origin; GURL::Replacements rep; rep.SetSchemeStr(url::kHttpScheme); - GURL http_origin = https_origin.ReplaceComponents(rep); + GURL http_origin = https_origin.GetURL().ReplaceComponents(rep); PasswordStore::FormDigest form(autofill::PasswordForm::Scheme::kHtml, http_origin.GetOrigin().spec(), http_origin); - http_origin_domain_ = http_origin.GetOrigin(); + http_origin_domain_ = url::Origin::Create(http_origin); client_->GetProfilePasswordStore()->GetLogins(form, this); client_->PostHSTSQueryForHost( https_origin, base::BindOnce(&OnHSTSQueryResultHelper, GetWeakPtr())); @@ -60,12 +60,12 @@ HttpPasswordStoreMigrator::~HttpPasswordStoreMigrator() = default; autofill::PasswordForm HttpPasswordStoreMigrator::MigrateHttpFormToHttps( const autofill::PasswordForm& http_form) { - DCHECK(http_form.origin.SchemeIs(url::kHttpScheme)); + DCHECK(http_form.url.SchemeIs(url::kHttpScheme)); autofill::PasswordForm https_form = http_form; GURL::Replacements rep; rep.SetSchemeStr(url::kHttpsScheme); - https_form.origin = http_form.origin.ReplaceComponents(rep); + https_form.url = http_form.url.ReplaceComponents(rep); // Only replace the scheme of the signon_realm in case it is HTTP. Do not // change the signon_realm for federated credentials. @@ -78,7 +78,7 @@ autofill::PasswordForm HttpPasswordStoreMigrator::MigrateHttpFormToHttps( // If |action| is not HTTPS then it's most likely obsolete. Otherwise, it // may still be valid. if (!http_form.action.SchemeIs(url::kHttpsScheme)) - https_form.action = https_form.origin; + https_form.action = https_form.url; https_form.form_data = autofill::FormData(); https_form.generation_upload_status = autofill::PasswordForm::GenerationUploadStatus::kNoSignalSent; @@ -103,7 +103,8 @@ void HttpPasswordStoreMigrator::OnHSTSQueryResult(HSTSResult is_hsts) { got_hsts_query_result_ = true; if (is_hsts == HSTSResult::kYes) - client_->GetProfilePasswordStore()->RemoveSiteStats(http_origin_domain_); + client_->GetProfilePasswordStore()->RemoveSiteStats( + http_origin_domain_.GetURL()); if (got_password_store_results_) ProcessPasswordStoreResults(); diff --git a/chromium/components/password_manager/core/browser/http_password_store_migrator.h b/chromium/components/password_manager/core/browser/http_password_store_migrator.h index 6f2f8f00595..53cb8b975ca 100644 --- a/chromium/components/password_manager/core/browser/http_password_store_migrator.h +++ b/chromium/components/password_manager/core/browser/http_password_store_migrator.h @@ -12,7 +12,7 @@ #include "base/sequence_checker.h" #include "components/password_manager/core/browser/hsts_query.h" #include "components/password_manager/core/browser/password_store_consumer.h" -#include "url/gurl.h" +#include "url/origin.h" namespace autofill { struct PasswordForm; @@ -57,7 +57,7 @@ class HttpPasswordStoreMigrator : public PasswordStoreConsumer { }; // |https_origin| should specify a valid HTTPS URL. - HttpPasswordStoreMigrator(const GURL& https_origin, + HttpPasswordStoreMigrator(const url::Origin& https_origin, const PasswordManagerClient* client, Consumer* consumer); ~HttpPasswordStoreMigrator() override; @@ -87,7 +87,7 @@ class HttpPasswordStoreMigrator : public PasswordStoreConsumer { bool got_password_store_results_ = false; HttpPasswordMigrationMode mode_ = HttpPasswordMigrationMode::kMove; std::vector<std::unique_ptr<autofill::PasswordForm>> results_; - GURL http_origin_domain_; + url::Origin http_origin_domain_; SEQUENCE_CHECKER(sequence_checker_); DISALLOW_COPY_AND_ASSIGN(HttpPasswordStoreMigrator); diff --git a/chromium/components/password_manager/core/browser/http_password_store_migrator_unittest.cc b/chromium/components/password_manager/core/browser/http_password_store_migrator_unittest.cc index 689de82cc32..db696d6d2b3 100644 --- a/chromium/components/password_manager/core/browser/http_password_store_migrator_unittest.cc +++ b/chromium/components/password_manager/core/browser/http_password_store_migrator_unittest.cc @@ -31,8 +31,8 @@ constexpr char kTestSubdomainHttpURL[] = "http://login.example.org/path2"; // Creates a dummy http form with some basic arbitrary values. PasswordForm CreateTestForm() { PasswordForm form; - form.origin = GURL(kTestHttpURL); - form.signon_realm = form.origin.GetOrigin().spec(); + form.url = GURL(kTestHttpURL); + form.signon_realm = form.url.GetOrigin().spec(); form.action = GURL("https://example.org/action.html"); form.username_value = base::ASCIIToUTF16("user"); form.password_value = base::ASCIIToUTF16("password"); @@ -42,8 +42,8 @@ PasswordForm CreateTestForm() { // Creates a dummy http PSL-matching form with some basic arbitrary values. PasswordForm CreateTestPSLForm() { PasswordForm form; - form.origin = GURL(kTestSubdomainHttpURL); - form.signon_realm = form.origin.GetOrigin().spec(); + form.url = GURL(kTestSubdomainHttpURL); + form.signon_realm = form.url.GetOrigin().spec(); form.action = GURL(kTestSubdomainHttpURL); form.username_value = base::ASCIIToUTF16("user2"); form.password_value = base::ASCIIToUTF16("password2"); @@ -57,7 +57,7 @@ PasswordForm CreateAndroidCredential() { form.username_value = base::ASCIIToUTF16("user3"); form.password_value = base::ASCIIToUTF16("password3"); form.signon_realm = "android://hash@com.example.android/"; - form.origin = GURL(form.signon_realm); + form.url = GURL(form.signon_realm); form.action = GURL(); form.is_affiliation_based_match = true; return form; @@ -68,7 +68,7 @@ PasswordForm CreateLocalFederatedCredential() { PasswordForm form; form.username_value = base::ASCIIToUTF16("user4"); form.signon_realm = "federation://localhost/federation.example.com"; - form.origin = GURL("http://localhost/"); + form.url = GURL("http://localhost/"); form.action = GURL("http://localhost/"); form.federation_origin = url::Origin::Create(GURL("https://federation.example.com")); @@ -97,20 +97,18 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { // PasswordManagerClient: PasswordStore* GetProfilePasswordStore() const override { return store_; } - void PostHSTSQueryForHost(const GURL& gurl, + void PostHSTSQueryForHost(const url::Origin& origin, HSTSCallback callback) const override { saved_callback_ = std::move(callback); - PostHSTSQueryForHostHelper(gurl); + PostHSTSQueryForHostHelper(origin); } - MOCK_CONST_METHOD1(PostHSTSQueryForHostHelper, void(const GURL&)); + MOCK_CONST_METHOD1(PostHSTSQueryForHostHelper, void(const url::Origin&)); HSTSCallback hsts_acquire_callback() { return std::move(saved_callback_); } private: PasswordStore* store_; mutable HSTSCallback saved_callback_; - - DISALLOW_COPY_AND_ASSIGN(MockPasswordManagerClient); }; } // namespace @@ -148,12 +146,14 @@ class HttpPasswordStoreMigratorTest : public testing::Test { }; void HttpPasswordStoreMigratorTest::TestEmptyStore(bool is_hsts) { - PasswordStore::FormDigest form(CreateTestForm()); - EXPECT_CALL(store(), GetLogins(form, _)); - EXPECT_CALL(client(), PostHSTSQueryForHostHelper(GURL(kTestHttpsURL))) + PasswordStore::FormDigest form_digest(CreateTestForm()); + form_digest.url = form_digest.url.GetOrigin(); + EXPECT_CALL(store(), GetLogins(form_digest, _)); + EXPECT_CALL(client(), PostHSTSQueryForHostHelper( + url::Origin::Create(GURL(kTestHttpsURL)))) .Times(1); - HttpPasswordStoreMigrator migrator(GURL(kTestHttpsURL), &client(), - &consumer()); + HttpPasswordStoreMigrator migrator(url::Origin::Create(GURL(kTestHttpsURL)), + &client(), &consumer()); HSTSCallback callback = client().hsts_acquire_callback(); std::move(callback).Run(is_hsts ? HSTSResult::kYes : HSTSResult::kNo); // We expect a potential call to |RemoveSiteStatsImpl| which is a async task @@ -170,11 +170,13 @@ void HttpPasswordStoreMigratorTest::TestEmptyStore(bool is_hsts) { void HttpPasswordStoreMigratorTest::TestFullStore(bool is_hsts) { PasswordStore::FormDigest form_digest(CreateTestForm()); + form_digest.url = form_digest.url.GetOrigin(); EXPECT_CALL(store(), GetLogins(form_digest, _)); - EXPECT_CALL(client(), PostHSTSQueryForHostHelper(GURL(kTestHttpsURL))) + EXPECT_CALL(client(), PostHSTSQueryForHostHelper( + url::Origin::Create(GURL(kTestHttpsURL)))) .Times(1); - HttpPasswordStoreMigrator migrator(GURL(kTestHttpsURL), &client(), - &consumer()); + HttpPasswordStoreMigrator migrator(url::Origin::Create(GURL(kTestHttpsURL)), + &client(), &consumer()); HSTSCallback callback = client().hsts_acquire_callback(); std::move(callback).Run(is_hsts ? HSTSResult::kYes : HSTSResult::kNo); // We expect a potential call to |RemoveSiteStatsImpl| which is a async task @@ -189,11 +191,11 @@ void HttpPasswordStoreMigratorTest::TestFullStore(bool is_hsts) { PasswordForm android_form = CreateAndroidCredential(); PasswordForm federated_form = CreateLocalFederatedCredential(); PasswordForm expected_form = form; - expected_form.origin = GURL(kTestHttpsURL); - expected_form.signon_realm = expected_form.origin.GetOrigin().spec(); + expected_form.url = GURL(kTestHttpsURL); + expected_form.signon_realm = expected_form.url.GetOrigin().spec(); PasswordForm expected_federated_form = federated_form; - expected_federated_form.origin = GURL("https://localhost"); + expected_federated_form.url = GURL("https://localhost"); expected_federated_form.action = GURL("https://localhost"); EXPECT_CALL(store(), AddLogin(expected_form)); @@ -218,13 +220,14 @@ void HttpPasswordStoreMigratorTest::TestMigratorDeletionByConsumer( bool is_hsts) { // Setup expectations on store and client. EXPECT_CALL(store(), GetLogins(_, _)); - EXPECT_CALL(client(), PostHSTSQueryForHostHelper(GURL(kTestHttpsURL))) + EXPECT_CALL(client(), PostHSTSQueryForHostHelper( + url::Origin::Create(GURL(kTestHttpsURL)))) .Times(1); // Construct the migrator, call |OnGetPasswordStoreResults| explicitly and // manually delete it. auto migrator = std::make_unique<HttpPasswordStoreMigrator>( - GURL(kTestHttpsURL), &client(), &consumer()); + url::Origin::Create(GURL(kTestHttpsURL)), &client(), &consumer()); migrator->OnGetPasswordStoreResults( std::vector<std::unique_ptr<autofill::PasswordForm>>()); EXPECT_CALL(consumer(), ProcessForms(_)).WillOnce(Invoke([&migrator](Unused) { @@ -271,17 +274,17 @@ TEST(HttpPasswordStoreMigrator, MigrateHttpFormToHttpsTestSignonRealm) { for (bool origin_has_paths : {true, false}) { PasswordForm http_html_form; - http_html_form.origin = kOrigins[origin_has_paths]; + http_html_form.url = kOrigins[origin_has_paths]; http_html_form.signon_realm = "http://example.org/"; http_html_form.scheme = PasswordForm::Scheme::kHtml; PasswordForm non_html_empty_realm_form; - non_html_empty_realm_form.origin = kOrigins[origin_has_paths]; + non_html_empty_realm_form.url = kOrigins[origin_has_paths]; non_html_empty_realm_form.signon_realm = "http://example.org/"; non_html_empty_realm_form.scheme = PasswordForm::Scheme::kBasic; PasswordForm non_html_form; - non_html_form.origin = kOrigins[origin_has_paths]; + non_html_form.url = kOrigins[origin_has_paths]; non_html_form.signon_realm = "http://example.org/realm"; non_html_form.scheme = PasswordForm::Scheme::kBasic; diff --git a/chromium/components/password_manager/core/browser/import/csv_password.cc b/chromium/components/password_manager/core/browser/import/csv_password.cc index 3628823f9cc..554eac73531 100644 --- a/chromium/components/password_manager/core/browser/import/csv_password.cc +++ b/chromium/components/password_manager/core/browser/import/csv_password.cc @@ -104,7 +104,7 @@ CSVPassword::Status CSVPassword::ParseImpl(PasswordForm* form) const { form->signon_realm = IsValidAndroidFacetURI(origin.spec()) ? origin.spec() : origin.GetOrigin().spec(); - form->origin = std::move(origin); + form->url = std::move(origin); form->username_value = Convert(username); form->password_value = Convert(password); form->date_created = base::Time::Now(); diff --git a/chromium/components/password_manager/core/browser/import/csv_password_sequence_unittest.cc b/chromium/components/password_manager/core/browser/import/csv_password_sequence_unittest.cc index 115d58f286a..50c8d27df07 100644 --- a/chromium/components/password_manager/core/browser/import/csv_password_sequence_unittest.cc +++ b/chromium/components/password_manager/core/browser/import/csv_password_sequence_unittest.cc @@ -113,7 +113,7 @@ TEST(CSVPasswordSequenceTest, Iteration) { ASSERT_LT(order, base::size(kExpectedCredentials)); PasswordForm parsed = pwd.ParseValid(); const auto& expected = kExpectedCredentials[order]; - EXPECT_EQ(GURL(expected.url), parsed.origin); + EXPECT_EQ(GURL(expected.url), parsed.url); EXPECT_EQ(base::ASCIIToUTF16(expected.username), parsed.username_value); EXPECT_EQ(base::ASCIIToUTF16(expected.password), parsed.password_value); ++order; @@ -129,7 +129,7 @@ TEST(CSVPasswordSequenceTest, MissingEolAtEof) { ASSERT_EQ(1, std::distance(seq.begin(), seq.end())); PasswordForm parsed = seq.begin()->ParseValid(); - EXPECT_EQ(GURL("http://a.com"), parsed.origin); + EXPECT_EQ(GURL("http://a.com"), parsed.url); EXPECT_EQ(base::ASCIIToUTF16("l"), parsed.username_value); EXPECT_EQ(base::ASCIIToUTF16("p"), parsed.password_value); } diff --git a/chromium/components/password_manager/core/browser/import/csv_password_unittest.cc b/chromium/components/password_manager/core/browser/import/csv_password_unittest.cc index 10d1f96d63b..8b38a94d416 100644 --- a/chromium/components/password_manager/core/browser/import/csv_password_unittest.cc +++ b/chromium/components/password_manager/core/browser/import/csv_password_unittest.cc @@ -32,7 +32,7 @@ TEST(CSVPasswordTest, Construction) { const CSVPassword csv_pwd(kColMap, "http://example.com,user,password"); const PasswordForm result = csv_pwd.ParseValid(); const GURL expected_origin("http://example.com"); - EXPECT_EQ(expected_origin, result.origin); + EXPECT_EQ(expected_origin, result.url); EXPECT_EQ(expected_origin.GetOrigin().spec(), result.signon_realm); EXPECT_EQ(base::ASCIIToUTF16("user"), result.username_value); EXPECT_EQ(base::ASCIIToUTF16("password"), result.password_value); @@ -54,7 +54,9 @@ struct TestCase { class TestCaseBuilder { public: - TestCaseBuilder(std::string name) { test_case_.name = std::move(name); } + explicit TestCaseBuilder(std::string name) { + test_case_.name = std::move(name); + } ~TestCaseBuilder() = default; @@ -114,7 +116,7 @@ TEST_P(CSVPasswordTestSuccess, Parse) { const PasswordForm result = csv_pwd.ParseValid(); const GURL expected_origin(test_case.origin); - EXPECT_EQ(expected_origin, result.origin); + EXPECT_EQ(expected_origin, result.url); EXPECT_EQ(expected_origin.GetOrigin().spec(), result.signon_realm); EXPECT_EQ(base::UTF8ToUTF16(test_case.username), result.username_value); diff --git a/chromium/components/password_manager/core/browser/import/password_importer_unittest.cc b/chromium/components/password_manager/core/browser/import/password_importer_unittest.cc index 00bd344014a..3b530f5d1b5 100644 --- a/chromium/components/password_manager/core/browser/import/password_importer_unittest.cc +++ b/chromium/components/password_manager/core/browser/import/password_importer_unittest.cc @@ -26,10 +26,7 @@ const char kTestFileName[] = "test_only.csv"; class PasswordImporterTest : public testing::Test { public: - PasswordImporterTest() - : callback_called_(false), result_(PasswordImporter::NUM_IMPORT_RESULTS) { - CHECK(temp_directory_.CreateUniqueTempDir()); - } + PasswordImporterTest() { CHECK(temp_directory_.CreateUniqueTempDir()); } protected: void StartImportAndWaitForCompletion(const base::FilePath& input_file) { @@ -65,8 +62,8 @@ class PasswordImporterTest : public testing::Test { private: base::test::TaskEnvironment task_environment_; - bool callback_called_; - PasswordImporter::Result result_; + bool callback_called_ = false; + PasswordImporter::Result result_ = PasswordImporter::NUM_IMPORT_RESULTS; std::vector<autofill::PasswordForm> imported_passwords_; DISALLOW_COPY_AND_ASSIGN(PasswordImporterTest); @@ -85,7 +82,7 @@ TEST_F(PasswordImporterTest, CSVImport) { EXPECT_EQ(PasswordImporter::SUCCESS, result()); ASSERT_EQ(1u, imported_passwords().size()); - EXPECT_EQ(GURL(kTestOriginURL), imported_passwords()[0].origin); + EXPECT_EQ(GURL(kTestOriginURL), imported_passwords()[0].url); EXPECT_EQ(kTestSignonRealm, imported_passwords()[0].signon_realm); EXPECT_EQ(base::ASCIIToUTF16(kTestUsername), imported_passwords()[0].username_value); diff --git a/chromium/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl_unittest.cc b/chromium/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl_unittest.cc index b85fa926bf1..1aec99fe4c4 100644 --- a/chromium/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl_unittest.cc +++ b/chromium/components/password_manager/core/browser/leak_detection/leak_detection_check_factory_impl_unittest.cc @@ -100,6 +100,9 @@ TEST_F(LeakDetectionCheckFactoryImplTest, BulkCheck_SignedInAndSyncing) { } TEST_F(LeakDetectionCheckFactoryImplTest, BulkCheck_FeatureOff) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature( + password_manager::features::kPasswordCheck); identity_env().SetPrimaryAccount(kTestAccount); EXPECT_FALSE(request_factory().TryCreateBulkLeakCheck( &bulk_delegate(), identity_env().identity_manager(), diff --git a/chromium/components/password_manager/core/browser/leak_detection_delegate.cc b/chromium/components/password_manager/core/browser/leak_detection_delegate.cc index ee6e9d1dfe9..ef1e159f295 100644 --- a/chromium/components/password_manager/core/browser/leak_detection_delegate.cc +++ b/chromium/components/password_manager/core/browser/leak_detection_delegate.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/browser/leak_detection_delegate.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/field_trial_params.h" #include "base/metrics/histogram_functions.h" #include "build/build_config.h" #include "components/autofill/core/common/password_form.h" @@ -61,7 +63,7 @@ void LeakDetectionDelegate::StartLeakCheck(const autofill::PasswordForm& form) { helper_.reset(); if (leak_check_) { is_leaked_timer_ = std::make_unique<base::ElapsedTimer>(); - leak_check_->Start(form.origin, form.username_value, form.password_value); + leak_check_->Start(form.url, form.username_value, form.password_value); } } @@ -75,16 +77,20 @@ void LeakDetectionDelegate::OnLeakDetectionDone(bool is_leaked, logger.LogBoolean(Logger::STRING_LEAK_DETECTION_FINISHED, is_leaked); } - if (is_leaked) { - // Otherwise query the helper to asynchronously determine the - // |CredentialLeakType|. - helper_ = std::make_unique<LeakDetectionDelegateHelper>( - client_->GetProfilePasswordStore(), - base::BindOnce( - &LeakDetectionDelegate::OnShowLeakDetectionNotification, - base::Unretained(this))); - helper_->ProcessLeakedPassword(std::move(url), std::move(username), - std::move(password)); + bool force_dialog_for_testing = base::GetFieldTrialParamByFeatureAsBool( + password_manager::features::kPasswordChange, + password_manager::features:: + kPasswordChangeWithForcedDialogAfterEverySuccessfulSubmission, + false); + if (is_leaked || force_dialog_for_testing) { + // Otherwise query the helper to asynchronously determine the + // |CredentialLeakType|. + helper_ = std::make_unique<LeakDetectionDelegateHelper>( + client_->GetProfilePasswordStore(), + base::BindOnce(&LeakDetectionDelegate::OnShowLeakDetectionNotification, + base::Unretained(this))); + helper_->ProcessLeakedPassword(std::move(url), std::move(username), + std::move(password)); } } @@ -93,6 +99,22 @@ void LeakDetectionDelegate::OnShowLeakDetectionNotification( IsReused is_reused, GURL url, base::string16 username) { + bool force_dialog_for_testing = base::GetFieldTrialParamByFeatureAsBool( + password_manager::features::kPasswordChange, + password_manager::features:: + kPasswordChangeWithForcedDialogAfterEverySuccessfulSubmission, + false); + if (force_dialog_for_testing) { + helper_.reset(); + // Correct leak_type to offer change password. + CredentialLeakType leak_type = + CreateLeakType(is_saved, IsReused(false), + IsSyncing(client_->GetPasswordSyncState() == + SYNCING_NORMAL_ENCRYPTION)); + client_->NotifyUserCredentialsWereLeaked(leak_type, url, username); + return; + } + DCHECK(is_leaked_timer_); base::UmaHistogramTimes("PasswordManager.LeakDetection.NotifyIsLeakedTime", std::exchange(is_leaked_timer_, nullptr)->Elapsed()); diff --git a/chromium/components/password_manager/core/browser/leak_detection_delegate_helper.cc b/chromium/components/password_manager/core/browser/leak_detection_delegate_helper.cc index 17eae1007dc..e9e7dc1bff6 100644 --- a/chromium/components/password_manager/core/browser/leak_detection_delegate_helper.cc +++ b/chromium/components/password_manager/core/browser/leak_detection_delegate_helper.cc @@ -46,7 +46,7 @@ void LeakDetectionDelegateHelper::OnGetPasswordStoreResults( IsSaved is_saved( std::any_of(results.begin(), results.end(), [this](const auto& form) { - return form->origin == url_ && form->username_value == username_; + return form->url == url_ && form->username_value == username_; })); IsReused is_reused(results.size() > (is_saved ? 1 : 0)); diff --git a/chromium/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc b/chromium/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc index c58890c36ed..bdef2803db9 100644 --- a/chromium/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc +++ b/chromium/components/password_manager/core/browser/leak_detection_delegate_helper_unittest.cc @@ -43,10 +43,10 @@ PasswordForm CreateForm(base::StringPiece origin, base::StringPiece username, base::StringPiece password = kLeakedPassword) { PasswordForm form; - form.origin = GURL(ASCIIToUTF16(origin)); + form.url = GURL(ASCIIToUTF16(origin)); form.username_value = ASCIIToUTF16(username); form.password_value = ASCIIToUTF16(password); - form.signon_realm = form.origin.GetOrigin().spec(); + form.signon_realm = form.url.GetOrigin().spec(); return form; } @@ -122,6 +122,8 @@ TEST_F(LeakDetectionDelegateHelperTest, SavedLeakedCredentials) { SetGetLoginByPasswordConsumerInvocation(std::move(password_forms)); SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(false)); + EXPECT_CALL(*store_, AddCompromisedCredentialsImpl) + .Times(base::FeatureList::IsEnabled(features::kPasswordCheck)); InitiateGetCredentialLeakType(); } @@ -134,6 +136,8 @@ TEST_F(LeakDetectionDelegateHelperTest, SetGetLoginByPasswordConsumerInvocation(std::move(password_forms)); SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true)); + EXPECT_CALL(*store_, AddCompromisedCredentialsImpl) + .Times(2 * base::FeatureList::IsEnabled(features::kPasswordCheck)); InitiateGetCredentialLeakType(); } @@ -147,6 +151,8 @@ TEST_F(LeakDetectionDelegateHelperTest, SetGetLoginByPasswordConsumerInvocation(std::move(password_forms)); SetOnShowLeakDetectionNotificationExpectation(IsSaved(true), IsReused(true)); + EXPECT_CALL(*store_, AddCompromisedCredentialsImpl) + .Times(base::FeatureList::IsEnabled(features::kPasswordCheck)); InitiateGetCredentialLeakType(); } @@ -167,6 +173,8 @@ TEST_F(LeakDetectionDelegateHelperTest, ReusedPasswordOnOtherOrigin) { SetGetLoginByPasswordConsumerInvocation(std::move(password_forms)); SetOnShowLeakDetectionNotificationExpectation(IsSaved(false), IsReused(true)); + EXPECT_CALL(*store_, AddCompromisedCredentialsImpl) + .Times(base::FeatureList::IsEnabled(features::kPasswordCheck)); InitiateGetCredentialLeakType(); } diff --git a/chromium/components/password_manager/core/browser/leak_detection_delegate_unittest.cc b/chromium/components/password_manager/core/browser/leak_detection_delegate_unittest.cc index edea5aa94bc..40c50661b59 100644 --- a/chromium/components/password_manager/core/browser/leak_detection_delegate_unittest.cc +++ b/chromium/components/password_manager/core/browser/leak_detection_delegate_unittest.cc @@ -40,7 +40,7 @@ using testing::WithArg; autofill::PasswordForm CreateTestForm() { autofill::PasswordForm form; - form.origin = GURL("http://www.example.com/a/LoginAuth"); + form.url = GURL("http://www.example.com/a/LoginAuth"); form.username_value = ASCIIToUTF16("Adam"); form.password_value = ASCIIToUTF16("p4ssword"); form.signon_realm = "http://www.example.com/"; @@ -164,7 +164,7 @@ TEST_F(LeakDetectionDelegateTest, StartCheck) { EXPECT_CALL(client(), IsIncognito).WillOnce(Return(false)); auto check_instance = std::make_unique<MockLeakDetectionCheck>(); EXPECT_CALL(*check_instance, - Start(form.origin, form.username_value, form.password_value)); + Start(form.url, form.username_value, form.password_value)); EXPECT_CALL(factory(), TryCreateLeakCheck(&delegate(), _, _)) .WillOnce(Return(ByMove(std::move(check_instance)))); delegate().StartLeakCheck(form); @@ -190,7 +190,7 @@ TEST_F(LeakDetectionDelegateTest, StartCheckWithStandardProtection) { EXPECT_CALL(client(), IsIncognito).WillOnce(Return(false)); auto check_instance = std::make_unique<MockLeakDetectionCheck>(); EXPECT_CALL(*check_instance, - Start(form.origin, form.username_value, form.password_value)); + Start(form.url, form.username_value, form.password_value)); EXPECT_CALL(factory(), TryCreateLeakCheck(&delegate(), _, _)) .WillOnce(Return(ByMove(std::move(check_instance)))); delegate().StartLeakCheck(form); @@ -209,7 +209,7 @@ TEST_F(LeakDetectionDelegateTest, StartCheckWithEnhancedProtection) { EXPECT_CALL(client(), IsIncognito).WillOnce(Return(false)); auto check_instance = std::make_unique<MockLeakDetectionCheck>(); EXPECT_CALL(*check_instance, - Start(form.origin, form.username_value, form.password_value)); + Start(form.url, form.username_value, form.password_value)); EXPECT_CALL(factory(), TryCreateLeakCheck(&delegate(), _, _)) .WillOnce(Return(ByMove(std::move(check_instance)))); delegate().StartLeakCheck(form); @@ -256,12 +256,47 @@ TEST_F(LeakDetectionDelegateTest, LeakDetectionDoneWithFalseResult) { EXPECT_CALL(client(), NotifyUserCredentialsWereLeaked).Times(0); delegate_interface->OnLeakDetectionDone( - /*is_leaked=*/false, form.origin, form.username_value, - form.password_value); + /*is_leaked=*/false, form.url, form.username_value, form.password_value); histogram_tester.ExpectTotalCount( "PasswordManager.LeakDetection.NotifyIsLeakedTime", 0); } +TEST_F(LeakDetectionDelegateTest, + LeakDetectionWithForcedDialogAfterEverySuccessfulSubmission) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeatureWithParameters( + features::kPasswordChange, + {{features::kPasswordChangeWithForcedDialogAfterEverySuccessfulSubmission, + "true"}}); + + EXPECT_TRUE(base::GetFieldTrialParamByFeatureAsBool( + password_manager::features::kPasswordChange, + password_manager::features:: + kPasswordChangeWithForcedDialogAfterEverySuccessfulSubmission, + false)); + + LeakDetectionDelegateInterface* delegate_interface = &delegate(); + const autofill::PasswordForm form = CreateTestForm(); + + EXPECT_CALL(client(), GetProfilePasswordStore()) + .WillRepeatedly(testing::Return(store())); + EXPECT_CALL(*store(), FillMatchingLoginsByPassword); + EXPECT_CALL(factory(), TryCreateLeakCheck) + .WillOnce( + Return(ByMove(std::make_unique<NiceMock<MockLeakDetectionCheck>>()))); + delegate().StartLeakCheck(form); + + EXPECT_CALL(client(), + NotifyUserCredentialsWereLeaked( + password_manager::CreateLeakType( + IsSaved(false), IsReused(false), IsSyncing(false)), + form.url, form.username_value)); + + delegate_interface->OnLeakDetectionDone( + /*is_leaked=*/false, form.url, form.username_value, form.password_value); + WaitForPasswordStore(); +} + TEST_F(LeakDetectionDelegateTest, LeakDetectionDoneWithTrueResult) { base::HistogramTester histogram_tester; LeakDetectionDelegateInterface* delegate_interface = &delegate(); @@ -279,10 +314,9 @@ TEST_F(LeakDetectionDelegateTest, LeakDetectionDoneWithTrueResult) { NotifyUserCredentialsWereLeaked( password_manager::CreateLeakType( IsSaved(false), IsReused(false), IsSyncing(false)), - form.origin, form.username_value)); + form.url, form.username_value)); delegate_interface->OnLeakDetectionDone( - /*is_leaked=*/true, form.origin, form.username_value, - form.password_value); + /*is_leaked=*/true, form.url, form.username_value, form.password_value); WaitForPasswordStore(); histogram_tester.ExpectTotalCount( "PasswordManager.LeakDetection.NotifyIsLeakedTime", 1); @@ -305,14 +339,13 @@ TEST_F(LeakDetectionDelegateTest, LeakHistoryAddCredentials) { Return(ByMove(std::make_unique<NiceMock<MockLeakDetectionCheck>>()))); delegate().StartLeakCheck(form); - EXPECT_CALL(client(), NotifyUserCredentialsWereLeaked(_, form.origin, + EXPECT_CALL(client(), NotifyUserCredentialsWereLeaked(_, form.url, form.username_value)); delegate_interface->OnLeakDetectionDone( - /*is_leaked=*/true, form.origin, form.username_value, - form.password_value); + /*is_leaked=*/true, form.url, form.username_value, form.password_value); const CompromisedCredentials compromised_credentials = { - GetSignonRealm(form.origin), form.username_value, base::Time::Now(), + GetSignonRealm(form.url), form.username_value, base::Time::Now(), CompromiseType::kLeaked}; EXPECT_CALL(*store(), AddCompromisedCredentialsImpl(compromised_credentials)); WaitForPasswordStore(); @@ -334,8 +367,7 @@ TEST_F(LeakDetectionDelegateTest, CallStartTwice) { // The delegate analyses the password store after this call. LeakDetectionDelegateInterface* delegate_interface = &delegate(); delegate_interface->OnLeakDetectionDone( - /*is_leaked=*/true, form.origin, form.username_value, - form.password_value); + /*is_leaked=*/true, form.url, form.username_value, form.password_value); // Start the check again on another form in the mean time. check_instance = std::make_unique<NiceMock<MockLeakDetectionCheck>>(); @@ -352,8 +384,7 @@ TEST_F(LeakDetectionDelegateTest, CallStartTwice) { // The second check is finishing and talking to the password store. It should // not crash. delegate_interface->OnLeakDetectionDone( - /*is_leaked=*/true, form.origin, form.username_value, - form.password_value); + /*is_leaked=*/true, form.url, form.username_value, form.password_value); WaitForPasswordStore(); } diff --git a/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.cc b/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.cc index c0db868c259..f3b9ac7495b 100644 --- a/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.cc +++ b/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.cc @@ -4,6 +4,7 @@ #include "components/password_manager/core/browser/leak_detection_dialog_utils.h" +#include "base/feature_list.h" #include "base/metrics/field_trial_params.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" @@ -55,8 +56,17 @@ base::string16 GetFormattedUrl(const GURL& origin) { } base::string16 GetAcceptButtonLabel(CredentialLeakType leak_type) { - return l10n_util::GetStringUTF16( - ShouldCheckPasswords(leak_type) ? IDS_LEAK_CHECK_CREDENTIALS : IDS_OK); + // |ShouldShowChangePasswordButton()| and |ShouldCheckPasswords()| are not + // both true at the same time. + if (ShouldCheckPasswords(leak_type)) { + return l10n_util::GetStringUTF16(IDS_LEAK_CHECK_CREDENTIALS); + } + + if (ShouldShowChangePasswordButton(leak_type)) { + return l10n_util::GetStringUTF16(IDS_PASSWORD_CHANGE); + } + + return l10n_util::GetStringUTF16(IDS_OK); } base::string16 GetCancelButtonLabel() { @@ -97,8 +107,27 @@ bool ShouldCheckPasswords(CredentialLeakType leak_type) { password_manager::IsSyncingPasswordsNormally(leak_type); } +bool ShouldShowChangePasswordButton(CredentialLeakType leak_type) { + if (!base::FeatureList::IsEnabled( + password_manager::features::kPasswordChange)) { + return false; + } + + // Password change should be offered if all following conditions are + // fulfilled: + // - password is saved (The password change flows will automatically save the + // password. This should only happen as an update of an existing entry.) + // - sync is on (because the password change flow relies on password + // generation which is only available to sync users). + // - password is not used on the other sites (TODO(crbug/1086114): to be + // removed when we have proper UI). + return IsPasswordSaved(leak_type) && !IsPasswordUsedOnOtherSites(leak_type) && + IsSyncingPasswordsNormally(leak_type); +} + bool ShouldShowCancelButton(CredentialLeakType leak_type) { - return ShouldCheckPasswords(leak_type); + return ShouldCheckPasswords(leak_type) || + ShouldShowChangePasswordButton(leak_type); } LeakDialogType GetLeakDialogType(CredentialLeakType leak_type) { diff --git a/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.h b/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.h index 2702e708955..bb907274f12 100644 --- a/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.h +++ b/chromium/components/password_manager/core/browser/leak_detection_dialog_utils.h @@ -74,6 +74,9 @@ base::string16 GetLeakDetectionTooltip(); // Checks whether the leak dialog should prompt user to password checkup. bool ShouldCheckPasswords(password_manager::CredentialLeakType leak_type); +// Checks whether the leak dialog should show change password button. +bool ShouldShowChangePasswordButton(CredentialLeakType leak_type); + // Checks whether the leak dialog should show cancel button. bool ShouldShowCancelButton(password_manager::CredentialLeakType leak_type); diff --git a/chromium/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc b/chromium/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc index 0aea00c147c..861ec2ae9d8 100644 --- a/chromium/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc +++ b/chromium/components/password_manager/core/browser/leak_detection_dialog_utils_unittest.cc @@ -6,6 +6,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/test/scoped_feature_list.h" +#include "build/build_config.h" #include "components/password_manager/core/common/password_manager_features.h" #include "components/strings/grit/components_strings.h" #include "components/url_formatter/elide_url.h" @@ -198,4 +199,69 @@ INSTANTIATE_TEST_SUITE_P(InstantiationName, BulkCheckCredentialLeakDialogUtilsTest, testing::ValuesIn(kBulkCheckTestCases)); +#if defined(OS_ANDROID) +struct PasswordChangeParams { + // Specifies the test case. + CredentialLeakType leak_type; + int accept_button_id; + bool should_show_cancel_button; + bool should_show_change_password_button; +} kPasswordChangeTestCases[] = { + {CreateLeakType(IsSaved(false), IsReused(false), IsSyncing(false)), IDS_OK, + false, false}, + {CreateLeakType(IsSaved(false), IsReused(false), IsSyncing(true)), IDS_OK, + false, false}, + {CreateLeakType(IsSaved(false), IsReused(true), IsSyncing(false)), IDS_OK, + false, false}, + {CreateLeakType(IsSaved(false), IsReused(true), IsSyncing(true)), + IDS_LEAK_CHECK_CREDENTIALS, true, false}, + {CreateLeakType(IsSaved(true), IsReused(false), IsSyncing(false)), IDS_OK, + false, false}, + {CreateLeakType(IsSaved(true), IsReused(false), IsSyncing(true)), + IDS_PASSWORD_CHANGE, true, true}, + {CreateLeakType(IsSaved(true), IsReused(true), IsSyncing(false)), IDS_OK, + false, false}, + {CreateLeakType(IsSaved(true), IsReused(true), IsSyncing(true)), + IDS_LEAK_CHECK_CREDENTIALS, true, false}}; + +class PasswordChangeCredentialLeakDialogUtilsTest + : public testing::TestWithParam<PasswordChangeParams> { + public: + PasswordChangeCredentialLeakDialogUtilsTest() { + feature_list_.InitAndEnableFeature(features::kPasswordChange); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +TEST_P(PasswordChangeCredentialLeakDialogUtilsTest, + ShouldShowChangePasswordButton) { + SCOPED_TRACE(testing::Message() << GetParam().leak_type); + + // ShouldCheckPasswords and ShouldShowChangePasswordButton + // should never be true both. + EXPECT_FALSE(ShouldCheckPasswords(GetParam().leak_type) && + ShouldShowChangePasswordButton(GetParam().leak_type)); + + EXPECT_EQ(GetParam().should_show_change_password_button, + ShouldShowChangePasswordButton(GetParam().leak_type)); +} + +TEST_P(PasswordChangeCredentialLeakDialogUtilsTest, ShouldShowCancelButton) { + SCOPED_TRACE(testing::Message() << GetParam().leak_type); + EXPECT_EQ(GetParam().should_show_cancel_button, + ShouldShowCancelButton(GetParam().leak_type)); +} + +TEST_P(PasswordChangeCredentialLeakDialogUtilsTest, GetAcceptButtonLabel) { + SCOPED_TRACE(testing::Message() << GetParam().leak_type); + EXPECT_EQ(l10n_util::GetStringUTF16(GetParam().accept_button_id), + GetAcceptButtonLabel(GetParam().leak_type)); +} + +INSTANTIATE_TEST_SUITE_P(InstantiationName, + PasswordChangeCredentialLeakDialogUtilsTest, + testing::ValuesIn(kPasswordChangeTestCases)); +#endif } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/login_database.cc b/chromium/components/password_manager/core/browser/login_database.cc index a4f62f50250..73c22b24e1d 100644 --- a/chromium/components/password_manager/core/browser/login_database.cc +++ b/chromium/components/password_manager/core/browser/login_database.cc @@ -66,9 +66,9 @@ const int kCompatibleVersionNumber = 19; base::Pickle SerializeValueElementPairs( const autofill::ValueElementVector& vec) { base::Pickle p; - for (size_t i = 0; i < vec.size(); ++i) { - p.WriteString16(vec[i].first); - p.WriteString16(vec[i].second); + for (const auto& pair : vec) { + p.WriteString16(pair.first); + p.WriteString16(pair.second); } return p; } @@ -199,7 +199,7 @@ struct SQLTableBuilders { }; void BindAddStatement(const PasswordForm& form, sql::Statement* s) { - s->BindString(COLUMN_ORIGIN_URL, form.origin.spec()); + s->BindString(COLUMN_ORIGIN_URL, form.url.spec()); s->BindString(COLUMN_ACTION_URL, form.action.spec()); s->BindString16(COLUMN_USERNAME_ELEMENT, form.username_element); s->BindString16(COLUMN_USERNAME_VALUE, form.username_value); @@ -251,7 +251,7 @@ void AddCallback(int* output_err, int err, sql::Statement* /*stmt*/) { } bool DoesMatchConstraints(const PasswordForm& form) { - if (!IsValidAndroidFacetURI(form.signon_realm) && form.origin.is_empty()) { + if (!IsValidAndroidFacetURI(form.signon_realm) && form.url.is_empty()) { DLOG(ERROR) << "Constraint violation: form.origin is empty"; return false; } @@ -279,10 +279,13 @@ void LogTimesUsedStat(const std::string& name, int sample) { base::UmaHistogramCustomCounts(name, sample, 0, 100, 10); } -void LogNumberOfAccountsForScheme(const std::string& scheme, int sample) { +void LogNumberOfAccountsForScheme(base::StringPiece suffix_for_store, + const std::string& scheme, + int sample) { base::UmaHistogramCustomCounts( - "PasswordManager.TotalAccountsHiRes.WithScheme." + scheme, sample, 1, - 1000, 100); + base::StrCat({kPasswordManager, suffix_for_store, + ".TotalAccountsHiRes.WithScheme.", scheme}), + sample, 1, 1000, 100); } bool ClearAllSyncMetadata(sql::Database* db) { @@ -602,7 +605,7 @@ std::string GeneratePlaceholders(size_t count) { // and returns it. PasswordForm GetFormForRemoval(const sql::Statement& statement) { PasswordForm form; - form.origin = GURL(statement.ColumnString(COLUMN_ORIGIN_URL)); + form.url = GURL(statement.ColumnString(COLUMN_ORIGIN_URL)); form.username_element = statement.ColumnString16(COLUMN_USERNAME_ELEMENT); form.username_value = statement.ColumnString16(COLUMN_USERNAME_VALUE); form.password_element = statement.ColumnString16(COLUMN_PASSWORD_ELEMENT); @@ -612,6 +615,12 @@ PasswordForm GetFormForRemoval(const sql::Statement& statement) { } // namespace +struct LoginDatabase::PrimaryKeyAndPassword { + int primary_key; + std::string encrypted_password; + base::string16 decrypted_password; +}; + LoginDatabase::LoginDatabase(const base::FilePath& db_path, IsAccountStore is_account_store) : db_path_(db_path), is_account_store_(is_account_store) {} @@ -866,7 +875,7 @@ void LoginDatabase::ReportNumberOfAccountsMetrics( blacklisted_sites); } -void LoginDatabase::RecordTimesPasswordUsedMetrics( +void LoginDatabase::ReportTimesPasswordUsedMetrics( bool custom_passphrase_sync_enabled) { sql::Statement usage_statement(db_.GetCachedStatement( SQL_FROM_HERE, "SELECT password_type, times_used FROM logins")); @@ -939,8 +948,10 @@ void LoginDatabase::ReportEmptyUsernamesMetrics() { "WHERE blacklisted_by_user=0 AND username_value=''")); if (empty_usernames_statement.Step()) { int empty_forms = empty_usernames_statement.ColumnInt(0); - UMA_HISTOGRAM_COUNTS_100("PasswordManager.EmptyUsernames.CountInDatabase", - empty_forms); + base::UmaHistogramCounts100( + base::StrCat({kPasswordManager, GetMetricsSuffixForStore(), + ".EmptyUsernames.CountInDatabase"}), + empty_forms); } } @@ -977,11 +988,13 @@ void LoginDatabase::ReportLoginsWithSchemesMetrics() { } } - LogNumberOfAccountsForScheme("Android", android_logins); - LogNumberOfAccountsForScheme("Ftp", ftp_logins); - LogNumberOfAccountsForScheme("Http", http_logins); - LogNumberOfAccountsForScheme("Https", https_logins); - LogNumberOfAccountsForScheme("Other", other_logins); + base::StringPiece suffix_for_store = GetMetricsSuffixForStore(); + + LogNumberOfAccountsForScheme(suffix_for_store, "Android", android_logins); + LogNumberOfAccountsForScheme(suffix_for_store, "Ftp", ftp_logins); + LogNumberOfAccountsForScheme(suffix_for_store, "Http", http_logins); + LogNumberOfAccountsForScheme(suffix_for_store, "Https", https_logins); + LogNumberOfAccountsForScheme(suffix_for_store, "Other", other_logins); } void LoginDatabase::ReportBubbleSuppressionMetrics() { @@ -1015,8 +1028,10 @@ void LoginDatabase::ReportInaccessiblePasswordsMetrics() { ++failed_encryption; } } - UMA_HISTOGRAM_COUNTS_100("PasswordManager.InaccessiblePasswords", - failed_encryption); + base::UmaHistogramCounts100( + base::StrCat({kPasswordManager, GetMetricsSuffixForStore(), + ".InaccessiblePasswords"}), + failed_encryption); } void LoginDatabase::ReportDuplicateCredentialsMetrics() { @@ -1083,20 +1098,23 @@ void LoginDatabase::ReportMetrics(const std::string& sync_username, TRACE_EVENT0("passwords", "LoginDatabase::ReportMetrics"); ReportNumberOfAccountsMetrics(custom_passphrase_sync_enabled); - RecordTimesPasswordUsedMetrics(custom_passphrase_sync_enabled); + ReportLoginsWithSchemesMetrics(); + ReportTimesPasswordUsedMetrics(custom_passphrase_sync_enabled); + ReportEmptyUsernamesMetrics(); + ReportInaccessiblePasswordsMetrics(); - // TODO(crbug.com/1063852): For now, don't record the remaining metrics for - // the account store, so as to not break the existing profile store metrics. - // Ultimately, many of these metrics should be recorded for both stores, but - // split into separate histograms. + // The remaining metrics are not recorded for the account store: + // - SyncingAccountState just doesn't make sense, since syncing users only use + // the profile store. + // - BubbleSuppression fields aren't used in the account store. + // - DuplicateCredentials *could* be recorded for the profile store, but are + // not very critical. + // - Compromised credentials are only stored in the profile store. if (is_account_store_.value()) return; ReportSyncingAccountStateMetrics(sync_username); - ReportEmptyUsernamesMetrics(); - ReportLoginsWithSchemesMetrics(); ReportBubbleSuppressionMetrics(); - ReportInaccessiblePasswordsMetrics(); ReportDuplicateCredentialsMetrics(); compromised_credentials_table_.ReportMetrics(bulk_check_done); @@ -1145,15 +1163,16 @@ PasswordStoreChangeList LoginDatabase::AddLogin(const PasswordForm& form, // Repeat the same statement but with REPLACE semantic. sqlite_error_code = 0; DCHECK(!add_replace_statement_.empty()); - const std::string encrpyted_old_password = GetEncryptedPassword(form); - bool password_changed = !encrpyted_old_password.empty() && - encrpyted_old_password != encrypted_password; - int old_primary_key = GetPrimaryKey(form); + PrimaryKeyAndPassword old_primary_key_password = + GetPrimaryKeyAndPassword(form); + bool password_changed = + form.password_value != old_primary_key_password.decrypted_password; s.Assign( db_.GetCachedStatement(SQL_FROM_HERE, add_replace_statement_.c_str())); BindAddStatement(form_with_encrypted_password, &s); if (s.Run()) { - list.emplace_back(PasswordStoreChange::REMOVE, form, old_primary_key); + list.emplace_back(PasswordStoreChange::REMOVE, form, + old_primary_key_password.primary_key); list.emplace_back(PasswordStoreChange::ADD, std::move(form_with_encrypted_password), db_.GetLastInsertRowId(), password_changed); @@ -1183,12 +1202,12 @@ PasswordStoreChangeList LoginDatabase::UpdateLogin(const PasswordForm& form, return PasswordStoreChangeList(); } - const std::string encrpyted_old_password = GetEncryptedPassword(form); - bool password_changed = !encrpyted_old_password.empty() && - encrpyted_old_password != encrypted_password; + const PrimaryKeyAndPassword old_primary_key_password = + GetPrimaryKeyAndPassword(form); #if defined(OS_IOS) - DeleteEncryptedPassword(form); + DeleteEncryptedPasswordFromKeychain( + old_primary_key_password.encrypted_password); #endif DCHECK(!update_statement_.empty()); sql::Statement s( @@ -1230,7 +1249,7 @@ PasswordStoreChangeList LoginDatabase::UpdateLogin(const PasswordForm& form, // If so, add new field below. // WHERE starts here. - s.BindString(next_param++, form.origin.spec()); + s.BindString(next_param++, form.url.spec()); s.BindString16(next_param++, form.username_element); s.BindString16(next_param++, form.username_value); s.BindString16(next_param++, form.password_element); @@ -1247,11 +1266,13 @@ PasswordStoreChangeList LoginDatabase::UpdateLogin(const PasswordForm& form, PasswordStoreChangeList list; if (db_.GetLastChangeCount()) { + bool password_changed = + form.password_value != old_primary_key_password.decrypted_password; PasswordForm form_with_encrypted_password = form; form_with_encrypted_password.encrypted_password = encrypted_password; list.emplace_back(PasswordStoreChange::UPDATE, std::move(form_with_encrypted_password), - GetPrimaryKey(form), password_changed); + old_primary_key_password.primary_key, password_changed); } else if (error) { *error = UpdateLoginError::kNoUpdatedRecords; } @@ -1265,15 +1286,17 @@ bool LoginDatabase::RemoveLogin(const PasswordForm& form, if (changes) { changes->clear(); } + const PrimaryKeyAndPassword old_primary_key_password = + GetPrimaryKeyAndPassword(form); #if defined(OS_IOS) - DeleteEncryptedPassword(form); + DeleteEncryptedPasswordFromKeychain( + old_primary_key_password.encrypted_password); #endif // Remove a login by UNIQUE-constrained fields. DCHECK(!delete_statement_.empty()); - int primary_key = GetPrimaryKey(form); sql::Statement s( db_.GetCachedStatement(SQL_FROM_HERE, delete_statement_.c_str())); - s.BindString(0, form.origin.spec()); + s.BindString(0, form.url.spec()); s.BindString16(1, form.username_element); s.BindString16(2, form.username_value); s.BindString16(3, form.password_element); @@ -1283,7 +1306,8 @@ bool LoginDatabase::RemoveLogin(const PasswordForm& form, return false; } if (changes) { - changes->emplace_back(PasswordStoreChange::REMOVE, form, primary_key, + changes->emplace_back(PasswordStoreChange::REMOVE, form, + old_primary_key_password.primary_key, /*password_changed=*/true); } return true; @@ -1341,7 +1365,7 @@ bool LoginDatabase::RemoveLoginsCreatedBetween( #if defined(OS_IOS) for (const auto& pair : key_to_form_map) { - DeleteEncryptedPassword(*pair.second); + DeleteEncryptedPasswordById(pair.first); } #endif @@ -1408,7 +1432,7 @@ LoginDatabase::EncryptionResult LoginDatabase::InitPasswordFormFromStatement( *primary_key = s.ColumnInt(COLUMN_ID); std::string tmp = s.ColumnString(COLUMN_ORIGIN_URL); - form->origin = GURL(tmp); + form->url = GURL(tmp); tmp = s.ColumnString(COLUMN_ACTION_URL); form->action = GURL(tmp); form->username_element = s.ColumnString16(COLUMN_USERNAME_ELEMENT); @@ -1537,7 +1561,7 @@ bool LoginDatabase::GetLogins( } } else if (should_federated_apply) { std::string expression = - base::StringPrintf("federation://%s/%%", form.origin.host().c_str()); + base::StringPrintf("federation://%s/%%", form.url.host().c_str()); s.BindString(placeholder++, expression); } @@ -1720,26 +1744,6 @@ DatabaseCleanupResult LoginDatabase::DeleteUndecryptableLogins() { return DatabaseCleanupResult::kSuccess; } -std::string LoginDatabase::GetEncryptedPassword( - const PasswordForm& form) const { - TRACE_EVENT0("passwords", "LoginDatabase::GetEncryptedPassword"); - DCHECK(!encrypted_statement_.empty()); - sql::Statement s( - db_.GetCachedStatement(SQL_FROM_HERE, encrypted_statement_.c_str())); - - s.BindString(0, form.origin.spec()); - s.BindString16(1, form.username_element); - s.BindString16(2, form.username_value); - s.BindString16(3, form.password_element); - s.BindString(4, form.signon_realm); - - std::string encrypted_password; - if (s.Step()) { - s.ColumnBlobAsString(0, &encrypted_password); - } - return encrypted_password; -} - std::unique_ptr<syncer::MetadataBatch> LoginDatabase::GetAllSyncMetadata() { TRACE_EVENT0("passwords", "LoginDatabase::GetAllSyncMetadata"); std::unique_ptr<syncer::MetadataBatch> metadata_batch = @@ -1760,7 +1764,15 @@ std::unique_ptr<syncer::MetadataBatch> LoginDatabase::GetAllSyncMetadata() { void LoginDatabase::DeleteAllSyncMetadata() { TRACE_EVENT0("passwords", "LoginDatabase::DeleteAllSyncMetadata"); + bool had_unsynced_deletions = HasUnsyncedDeletions(); ClearAllSyncMetadata(&db_); + if (had_unsynced_deletions && deletions_have_synced_callback_) { + // Note: At this point we can't be fully sure whether the deletions actually + // reached the server yet. We might have sent a commit, but haven't received + // the commit confirmation. Let's be conservative and assume they haven't + // been successfully deleted. + deletions_have_synced_callback_.Run(/*success=*/false); + } } bool LoginDatabase::UpdateSyncMetadata( @@ -1792,7 +1804,13 @@ bool LoginDatabase::UpdateSyncMetadata( s.BindInt(0, storage_key_int); s.BindString(1, encrypted_metadata); - return s.Run(); + bool had_unsynced_deletions = HasUnsyncedDeletions(); + bool result = s.Run(); + if (result && had_unsynced_deletions && !HasUnsyncedDeletions() && + deletions_have_synced_callback_) { + deletions_have_synced_callback_.Run(/*success=*/true); + } + return result; } bool LoginDatabase::ClearSyncMetadata(syncer::ModelType model_type, @@ -1813,7 +1831,13 @@ bool LoginDatabase::ClearSyncMetadata(syncer::ModelType model_type, "storage_key=?")); s.BindInt(0, storage_key_int); - return s.Run(); + bool had_unsynced_deletions = HasUnsyncedDeletions(); + bool result = s.Run(); + if (result && had_unsynced_deletions && !HasUnsyncedDeletions() && + deletions_have_synced_callback_) { + deletions_have_synced_callback_.Run(/*success=*/true); + } + return result; } bool LoginDatabase::UpdateModelTypeState( @@ -1843,6 +1867,26 @@ bool LoginDatabase::ClearModelTypeState(syncer::ModelType model_type) { return s.Run(); } +void LoginDatabase::SetDeletionsHaveSyncedCallback( + base::RepeatingCallback<void(bool)> callback) { + deletions_have_synced_callback_ = std::move(callback); +} + +bool LoginDatabase::HasUnsyncedDeletions() { + TRACE_EVENT0("passwords", "LoginDatabase::HasUnsyncedDeletions"); + + std::unique_ptr<syncer::MetadataBatch> batch = GetAllSyncEntityMetadata(); + if (!batch) + return false; + for (const auto& metadata_entry : batch->GetAllMetadata()) { + // Note: No need for an explicit "is unsynced" check: Once the deletion is + // committed, the metadata entry is removed. + if (metadata_entry.second->is_deleted()) + return true; + } + return false; +} + bool LoginDatabase::BeginTransaction() { TRACE_EVENT0("passwords", "LoginDatabase::BeginTransaction"); return db_.BeginTransaction(); @@ -1858,21 +1902,29 @@ bool LoginDatabase::CommitTransaction() { return db_.CommitTransaction(); } -int LoginDatabase::GetPrimaryKey(const PasswordForm& form) const { - DCHECK(!id_statement_.empty()); - sql::Statement s( - db_.GetCachedStatement(SQL_FROM_HERE, id_statement_.c_str())); +LoginDatabase::PrimaryKeyAndPassword LoginDatabase::GetPrimaryKeyAndPassword( + const PasswordForm& form) const { + DCHECK(!id_and_password_statement_.empty()); + sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, + id_and_password_statement_.c_str())); - s.BindString(0, form.origin.spec()); + s.BindString(0, form.url.spec()); s.BindString16(1, form.username_element); s.BindString16(2, form.username_value); s.BindString16(3, form.password_element); s.BindString(4, form.signon_realm); if (s.Step()) { - return s.ColumnInt(0); + PrimaryKeyAndPassword result = {s.ColumnInt(0)}; + s.ColumnBlobAsString(1, &result.encrypted_password); + if (DecryptedString(result.encrypted_password, + &result.decrypted_password) != + ENCRYPTION_RESULT_SUCCESS) { + result.decrypted_password.clear(); + } + return result; } - return -1; + return {-1, std::string(), base::string16()}; } std::unique_ptr<syncer::MetadataBatch> @@ -1979,20 +2031,10 @@ FormRetrievalResult LoginDatabase::StatementToForms( count_removed_logins++; } } - - if (count_removed_logins > 0) { - UMA_HISTOGRAM_COUNTS_100("PasswordManager.RemovedCorruptedPasswords", - count_removed_logins); - } - - if (count_removed_logins != forms_to_be_deleted.size()) { - metrics_util::LogDeleteCorruptedPasswordsResult( - metrics_util::DeleteCorruptedPasswordsResult::kItemFailure); - } else if (count_removed_logins > 0) { + if (count_removed_logins == forms_to_be_deleted.size() && + count_removed_logins > 0) { DCHECK(password_recovery_util_); password_recovery_util_->RecordPasswordRecovery(); - metrics_util::LogDeleteCorruptedPasswordsResult( - metrics_util::DeleteCorruptedPasswordsResult::kSuccessPasswordsDeleted); } #endif @@ -2059,14 +2101,12 @@ void LoginDatabase::InitializeStatementStrings(const SQLTableBuilder& builder) { blacklisted_statement_ = "SELECT " + all_column_names + " FROM logins WHERE blacklisted_by_user == ? ORDER BY origin_url"; - DCHECK(encrypted_statement_.empty()); - encrypted_statement_ = - "SELECT password_value FROM logins WHERE " + all_unique_key_column_names; DCHECK(encrypted_password_statement_by_id_.empty()); encrypted_password_statement_by_id_ = "SELECT password_value FROM logins WHERE id=?"; - DCHECK(id_statement_.empty()); - id_statement_ = "SELECT id FROM logins WHERE " + all_unique_key_column_names; + DCHECK(id_and_password_statement_.empty()); + id_and_password_statement_ = "SELECT id, password_value FROM logins WHERE " + + all_unique_key_column_names; } bool LoginDatabase::IsUsingCleanupMechanism() const { diff --git a/chromium/components/password_manager/core/browser/login_database.h b/chromium/components/password_manager/core/browser/login_database.h index f6f510bbe3d..88fa8f9460f 100644 --- a/chromium/components/password_manager/core/browser/login_database.h +++ b/chromium/components/password_manager/core/browser/login_database.h @@ -9,6 +9,7 @@ #include <string> #include <vector> +#include "base/callback.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/macros.h" @@ -175,10 +176,6 @@ class LoginDatabase : public PasswordStoreSync::MetadataStore { // removed from the database, returns ITEM_FAILURE. DatabaseCleanupResult DeleteUndecryptableLogins(); - // Returns the encrypted password value for the specified |form|. Returns an - // empty string if the row for this |form| is not found. - std::string GetEncryptedPassword(const autofill::PasswordForm& form) const; - // PasswordStoreSync::MetadataStore implementation. std::unique_ptr<syncer::MetadataBatch> GetAllSyncMetadata() override; void DeleteAllSyncMetadata() override; @@ -191,6 +188,9 @@ class LoginDatabase : public PasswordStoreSync::MetadataStore { syncer::ModelType model_type, const sync_pb::ModelTypeState& model_type_state) override; bool ClearModelTypeState(syncer::ModelType model_type) override; + void SetDeletionsHaveSyncedCallback( + base::RepeatingCallback<void(bool)> callback) override; + bool HasUnsyncedDeletions() override; // Callers that requires transaction support should call these methods to // begin, rollback and commit transactions. They delegate to the transaction @@ -214,16 +214,18 @@ class LoginDatabase : public PasswordStoreSync::MetadataStore { #endif // defined(OS_POSIX) && !defined(OS_MACOSX) private: + struct PrimaryKeyAndPassword; #if defined(OS_IOS) friend class LoginDatabaseIOSTest; FRIEND_TEST_ALL_PREFIXES(LoginDatabaseIOSTest, KeychainStorage); - // On iOS, removes the keychain item that is used to store the - // encrypted password for the supplied |form|. - void DeleteEncryptedPassword(const autofill::PasswordForm& form); + // Removes the keychain item corresponding to the look-up key |cipher_text|. + // It's stored as the encrypted password value. + static void DeleteEncryptedPasswordFromKeychain( + const std::string& cipher_text); - // Similar to DeleteEncryptedPassword() but uses |id| to look for the - // password. + // On iOS, removes the keychain item that is used to store the encrypted + // password for the supplied primary key |id|. void DeleteEncryptedPasswordById(int id); // Returns the encrypted password value for the specified |id|. Returns an @@ -236,7 +238,7 @@ class LoginDatabase : public PasswordStoreSync::MetadataStore { base::StringPiece GetMetricsSuffixForStore() const; void ReportNumberOfAccountsMetrics(bool custom_passphrase_sync_enabled); - void RecordTimesPasswordUsedMetrics(bool custom_passphrase_sync_enabled); + void ReportTimesPasswordUsedMetrics(bool custom_passphrase_sync_enabled); void ReportSyncingAccountStateMetrics(const std::string& sync_username); void ReportEmptyUsernamesMetrics(); void ReportLoginsWithSchemesMetrics(); @@ -295,9 +297,10 @@ class LoginDatabase : public PasswordStoreSync::MetadataStore { bool blacklisted, std::vector<std::unique_ptr<autofill::PasswordForm>>* forms); - // Returns the DB primary key for the specified |form|. Returns -1 if the row - // for this |form| is not found. - int GetPrimaryKey(const autofill::PasswordForm& form) const; + // Returns the DB primary key for the specified |form| and decrypted/encrypted + // password. Returns {-1, "", ""} if the row for this |form| is not found. + PrimaryKeyAndPassword GetPrimaryKeyAndPassword( + const autofill::PasswordForm& form) const; // Reads all the stored sync entities metadata in a MetadataBatch. Returns // nullptr in case of failure. @@ -349,9 +352,8 @@ class LoginDatabase : public PasswordStoreSync::MetadataStore { std::string get_statement_psl_federated_; std::string created_statement_; std::string blacklisted_statement_; - std::string encrypted_statement_; std::string encrypted_password_statement_by_id_; - std::string id_statement_; + std::string id_and_password_statement_; #if defined(OS_MACOSX) && !defined(OS_IOS) std::unique_ptr<PasswordRecoveryUtilMac> password_recovery_util_; @@ -364,6 +366,12 @@ class LoginDatabase : public PasswordStoreSync::MetadataStore { bool use_encryption_ = true; #endif // defined(OS_POSIX) + // A callback to be invoked whenever all pending deletions have been processed + // by Sync - see + // PasswordStoreSync::MetadataStore::SetDeletionsHaveSyncedCallback for more + // details. + base::RepeatingCallback<void(bool)> deletions_have_synced_callback_; + DISALLOW_COPY_AND_ASSIGN(LoginDatabase); }; diff --git a/chromium/components/password_manager/core/browser/login_database_ios.cc b/chromium/components/password_manager/core/browser/login_database_ios.cc index 9fae41caa85..a9d09bb7ae8 100644 --- a/chromium/components/password_manager/core/browser/login_database_ios.cc +++ b/chromium/components/password_manager/core/browser/login_database_ios.cc @@ -23,38 +23,6 @@ using autofill::PasswordForm; namespace password_manager { -namespace { - -void DeleteEncryptedPasswordFromKeychain(const std::string& cipher_text) { - if (cipher_text.empty()) - return; - - ScopedCFTypeRef<CFMutableDictionaryRef> query( - CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks)); - CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword); - - ScopedCFTypeRef<CFStringRef> item_ref( - base::SysUTF8ToCFStringRef(cipher_text)); - // We are using the account attribute to store item references. - CFDictionarySetValue(query, kSecAttrAccount, item_ref); - - OSStatus status = SecItemDelete(query); - if (status != errSecSuccess && status != errSecItemNotFound) { - NOTREACHED() << "Unable to remove password from keychain: " << status; - } - - // Delete the temporary passwords directory, since there might be leftover - // temporary files used for password export that contain the password being - // deleted. It can be called for a removal triggered by sync, which might - // happen at the same time as an export operation. In the unlikely event - // that the file is still needed by the consumer app, the export operation - // will fail. - password_manager::DeletePasswordsDirectory(); -} - -} // namespace - // On iOS, the LoginDatabase uses Keychain API to store passwords. The // "encrypted" version of the password is a unique ID (UUID) that is // stored as an attribute along with the password in the keychain. @@ -145,9 +113,34 @@ LoginDatabase::EncryptionResult LoginDatabase::DecryptedString( return ENCRYPTION_RESULT_SUCCESS; } -void LoginDatabase::DeleteEncryptedPassword(const PasswordForm& form) { - std::string cipher_text = GetEncryptedPassword(form); - DeleteEncryptedPasswordFromKeychain(cipher_text); +// static +void LoginDatabase::DeleteEncryptedPasswordFromKeychain( + const std::string& cipher_text) { + if (cipher_text.empty()) + return; + + ScopedCFTypeRef<CFMutableDictionaryRef> query( + CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword); + + ScopedCFTypeRef<CFStringRef> item_ref( + base::SysUTF8ToCFStringRef(cipher_text)); + // We are using the account attribute to store item references. + CFDictionarySetValue(query, kSecAttrAccount, item_ref); + + OSStatus status = SecItemDelete(query); + if (status != errSecSuccess && status != errSecItemNotFound) { + NOTREACHED() << "Unable to remove password from keychain: " << status; + } + + // Delete the temporary passwords directory, since there might be leftover + // temporary files used for password export that contain the password being + // deleted. It can be called for a removal triggered by sync, which might + // happen at the same time as an export operation. In the unlikely event + // that the file is still needed by the consumer app, the export operation + // will fail. + password_manager::DeletePasswordsDirectory(); } void LoginDatabase::DeleteEncryptedPasswordById(int id) { diff --git a/chromium/components/password_manager/core/browser/login_database_ios_unittest.cc b/chromium/components/password_manager/core/browser/login_database_ios_unittest.cc index e79dfc28337..72300f2dc95 100644 --- a/chromium/components/password_manager/core/browser/login_database_ios_unittest.cc +++ b/chromium/components/password_manager/core/browser/login_database_ios_unittest.cc @@ -107,7 +107,7 @@ TEST_F(LoginDatabaseIOSTest, AddLogin) { ASSERT_EQ(0U, GetKeychainSize()); PasswordForm form; - form.origin = GURL("http://0.com"); + form.url = GURL("http://0.com"); form.signon_realm = "http://www.example.com/"; form.action = GURL("http://www.example.com/action"); form.password_element = base::ASCIIToUTF16("pwd"); @@ -136,7 +136,7 @@ TEST_F(LoginDatabaseIOSTest, AddLogin) { TEST_F(LoginDatabaseIOSTest, UpdateLogin) { PasswordForm form; - form.origin = GURL("http://0.com"); + form.url = GURL("http://0.com"); form.signon_realm = "http://www.example.com"; form.action = GURL("http://www.example.com/action"); form.password_element = base::ASCIIToUTF16("pwd"); @@ -178,19 +178,19 @@ TEST_F(LoginDatabaseIOSTest, RemoveLogin) { TEST_F(LoginDatabaseIOSTest, RemoveLoginsCreatedBetween) { PasswordForm forms[3]; - forms[0].origin = GURL("http://0.com"); + forms[0].url = GURL("http://0.com"); forms[0].signon_realm = "http://www.example.com"; forms[0].username_element = base::ASCIIToUTF16("login0"); forms[0].date_created = base::Time::FromDoubleT(100); forms[0].password_value = base::ASCIIToUTF16("pass0"); - forms[1].origin = GURL("http://1.com"); + forms[1].url = GURL("http://1.com"); forms[1].signon_realm = "http://www.example.com"; forms[1].username_element = base::ASCIIToUTF16("login1"); forms[1].date_created = base::Time::FromDoubleT(200); forms[1].password_value = base::ASCIIToUTF16("pass1"); - forms[2].origin = GURL("http://2.com"); + forms[2].url = GURL("http://2.com"); forms[2].signon_realm = "http://www.example.com"; forms[2].username_element = base::ASCIIToUTF16("login2"); forms[2].date_created = base::Time::FromDoubleT(300); diff --git a/chromium/components/password_manager/core/browser/login_database_unittest.cc b/chromium/components/password_manager/core/browser/login_database_unittest.cc index 5af4423fbe0..d2c03d25779 100644 --- a/chromium/components/password_manager/core/browser/login_database_unittest.cc +++ b/chromium/components/password_manager/core/browser/login_database_unittest.cc @@ -71,7 +71,7 @@ PasswordStoreChangeList RemoveChangeForForm(const PasswordForm& form) { } void GenerateExamplePasswordForm(PasswordForm* form) { - form->origin = GURL("http://accounts.google.com/LoginAuth"); + form->url = GURL("http://accounts.google.com/LoginAuth"); form->action = GURL("http://accounts.google.com/Login"); form->username_element = ASCIIToUTF16("Email"); form->username_value = ASCIIToUTF16("test@gmail.com"); @@ -145,12 +145,12 @@ bool AddZeroClickableLogin(LoginDatabase* db, const GURL& origin) { // Example password form. PasswordForm form; - form.origin = origin; + form.url = origin; form.username_element = ASCIIToUTF16(unique_string); form.username_value = ASCIIToUTF16(unique_string); form.password_element = ASCIIToUTF16(unique_string); form.submit_element = ASCIIToUTF16("signIn"); - form.signon_realm = form.origin.spec(); + form.signon_realm = form.url.spec(); form.display_name = ASCIIToUTF16(unique_string); form.icon_url = origin; form.federation_origin = url::Origin::Create(origin); @@ -162,14 +162,14 @@ bool AddZeroClickableLogin(LoginDatabase* db, } MATCHER(IsGoogle1Account, "") { - return arg.origin.spec() == "https://accounts.google.com/ServiceLogin" && + return arg.url.spec() == "https://accounts.google.com/ServiceLogin" && arg.action.spec() == "https://accounts.google.com/ServiceLoginAuth" && arg.username_value == ASCIIToUTF16("theerikchen") && arg.scheme == PasswordForm::Scheme::kHtml; } MATCHER(IsGoogle2Account, "") { - return arg.origin.spec() == "https://accounts.google.com/ServiceLogin" && + return arg.url.spec() == "https://accounts.google.com/ServiceLogin" && arg.action.spec() == "https://accounts.google.com/ServiceLoginAuth" && arg.username_value == ASCIIToUTF16("theerikchen2") && arg.scheme == PasswordForm::Scheme::kHtml; @@ -209,7 +209,7 @@ class LoginDatabaseTest : public testing::Test { // Simple non-html auth form. PasswordForm non_html_auth; - non_html_auth.origin = GURL("http://example.com"); + non_html_auth.url = GURL("http://example.com"); non_html_auth.username_value = ASCIIToUTF16("test@gmail.com"); non_html_auth.password_value = ASCIIToUTF16("test"); non_html_auth.signon_realm = "http://example.com/Realm"; @@ -262,7 +262,7 @@ class LoginDatabaseTest : public testing::Test { std::string origin("http://56.7.8.90"); PasswordForm ip_form; - ip_form.origin = GURL(origin); + ip_form.url = GURL(origin); ip_form.username_value = ASCIIToUTF16("test@gmail.com"); ip_form.password_value = ASCIIToUTF16("test"); ip_form.signon_realm = origin; @@ -324,7 +324,7 @@ TEST_F(LoginDatabaseTest, Logins) { // The example site changes... PasswordForm form2(form); - form2.origin = GURL("http://www.google.com/new/accounts/LoginAuth"); + form2.url = GURL("http://www.google.com/new/accounts/LoginAuth"); form2.submit_element = ASCIIToUTF16("reallySignIn"); // Match against an inexact copy @@ -486,7 +486,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatching) { // Example password form. PasswordForm form; - form.origin = GURL("https://foo.com/"); + form.url = GURL("https://foo.com/"); form.action = GURL("https://foo.com/login"); form.username_element = ASCIIToUTF16("username"); form.username_value = ASCIIToUTF16("test@gmail.com"); @@ -509,7 +509,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatching) { // We go to the mobile site. PasswordForm form2(form); - form2.origin = GURL("https://mobile.foo.com/"); + form2.url = GURL("https://mobile.foo.com/"); form2.action = GURL("https://mobile.foo.com/login"); form2.signon_realm = "https://mobile.foo.com/"; @@ -525,7 +525,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatching) { // Example password form. PasswordForm form; - form.origin = GURL("https://foo.com/"); + form.url = GURL("https://foo.com/"); form.action = GURL("https://foo.com/login"); form.username_value = ASCIIToUTF16("test@gmail.com"); form.password_value = ASCIIToUTF16("test"); @@ -534,7 +534,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatching) { // We go to the mobile site. PasswordForm form2(form); - form2.origin = GURL("https://mobile.foo.com/"); + form2.url = GURL("https://mobile.foo.com/"); form2.action = GURL("https://mobile.foo.com/login"); form2.signon_realm = "federation://mobile.foo.com/accounts.google.com"; form2.username_value = ASCIIToUTF16("test1@gmail.com"); @@ -563,7 +563,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatching) { EXPECT_THAT(result, UnorderedElementsAre(Pointee(form), Pointee(form2))); // Match against the mobile site. - form_request.origin = GURL("https://mobile.foo.com/"); + form_request.url = GURL("https://mobile.foo.com/"); form_request.signon_realm = "https://mobile.foo.com/"; EXPECT_TRUE(db().GetLogins(form_request, &result)); // Both forms are matched, only form is a PSL match. @@ -574,7 +574,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatching) { TEST_F(LoginDatabaseTest, TestFederatedMatchingLocalhost) { PasswordForm form; - form.origin = GURL("http://localhost/"); + form.url = GURL("http://localhost/"); form.signon_realm = "federation://localhost/accounts.google.com"; form.federation_origin = url::Origin::Create(GURL("https://accounts.google.com/")); @@ -583,7 +583,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatchingLocalhost) { form.scheme = PasswordForm::Scheme::kHtml; PasswordForm form_with_port(form); - form_with_port.origin = GURL("http://localhost:8080/"); + form_with_port.url = GURL("http://localhost:8080/"); form_with_port.signon_realm = "federation://localhost/accounts.google.com"; EXPECT_EQ(AddChangeForForm(form), db().AddLogin(form)); @@ -601,7 +601,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatchingLocalhost) { EXPECT_TRUE(db().GetLogins(form_request, &result)); EXPECT_THAT(result, UnorderedElementsAre(Pointee(form))); - form_request.origin = GURL("http://localhost:8080/"); + form_request.url = GURL("http://localhost:8080/"); form_request.signon_realm = "http://localhost:8080/"; EXPECT_TRUE(db().GetLogins(form_request, &result)); EXPECT_THAT(result, UnorderedElementsAre(Pointee(form_with_port))); @@ -638,7 +638,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatchingShouldMatchingApply) { // Saved password form on Google sign-in page. PasswordForm form; - form.origin = GURL("https://accounts.google.com/"); + form.url = GURL("https://accounts.google.com/"); form.username_value = ASCIIToUTF16("test@gmail.com"); form.password_value = ASCIIToUTF16("test"); form.signon_realm = "https://accounts.google.com/"; @@ -680,7 +680,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatchingWithoutPSLMatching) { // Example password form. PasswordForm form; - form.origin = GURL("https://accounts.google.com/"); + form.url = GURL("https://accounts.google.com/"); form.action = GURL("https://accounts.google.com/login"); form.username_value = ASCIIToUTF16("test@gmail.com"); form.password_value = ASCIIToUTF16("test"); @@ -689,7 +689,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatchingWithoutPSLMatching) { // We go to a different site on the same domain where PSL is disabled. PasswordForm form2(form); - form2.origin = GURL("https://some.other.google.com/"); + form2.url = GURL("https://some.other.google.com/"); form2.action = GURL("https://some.other.google.com/login"); form2.signon_realm = "federation://some.other.google.com/accounts.google.com"; form2.username_value = ASCIIToUTF16("test1@gmail.com"); @@ -709,12 +709,12 @@ TEST_F(LoginDatabaseTest, TestFederatedMatchingWithoutPSLMatching) { // Match against the first one. PasswordStore::FormDigest form_request = {PasswordForm::Scheme::kHtml, - form.signon_realm, form.origin}; + form.signon_realm, form.url}; EXPECT_TRUE(db().GetLogins(form_request, &result)); EXPECT_THAT(result, testing::ElementsAre(Pointee(form))); // Match against the second one. - form_request.origin = form2.origin; + form_request.url = form2.url; form_request.signon_realm = form2.signon_realm; EXPECT_TRUE(db().GetLogins(form_request, &result)); form.is_public_suffix_match = true; @@ -724,7 +724,7 @@ TEST_F(LoginDatabaseTest, TestFederatedMatchingWithoutPSLMatching) { TEST_F(LoginDatabaseTest, TestFederatedPSLMatching) { // Save a federated credential for the PSL matched site. PasswordForm form; - form.origin = GURL("https://psl.example.com/"); + form.url = GURL("https://psl.example.com/"); form.action = GURL("https://psl.example.com/login"); form.signon_realm = "federation://psl.example.com/accounts.google.com"; form.username_value = ASCIIToUTF16("test1@gmail.com"); @@ -759,7 +759,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatchingDifferentSites) { // Example password form. PasswordForm form; - form.origin = GURL("https://foo.com/"); + form.url = GURL("https://foo.com/"); form.action = GURL("https://foo.com/login"); form.username_element = ASCIIToUTF16("username"); form.username_value = ASCIIToUTF16("test@gmail.com"); @@ -782,7 +782,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatchingDifferentSites) { // We go to the mobile site. PasswordStore::FormDigest form2(form); - form2.origin = GURL("https://mobile.foo.com/"); + form2.url = GURL("https://mobile.foo.com/"); form2.signon_realm = "https://mobile.foo.com/"; // Match against the mobile site. @@ -793,7 +793,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatchingDifferentSites) { result.clear(); // Add baz.com desktop site. - form.origin = GURL("https://baz.com/login/"); + form.url = GURL("https://baz.com/login/"); form.action = GURL("https://baz.com/login/"); form.username_element = ASCIIToUTF16("email"); form.username_value = ASCIIToUTF16("test@gmail.com"); @@ -811,7 +811,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatchingDifferentSites) { // We go to the mobile site of baz.com. PasswordStore::FormDigest form3(form); - form3.origin = GURL("https://m.baz.com/login/"); + form3.url = GURL("https://m.baz.com/login/"); form3.signon_realm = "https://m.baz.com/"; // Match against the mobile site of baz.com. @@ -825,7 +825,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatchingDifferentSites) { PasswordForm GetFormWithNewSignonRealm(PasswordForm form, std::string signon_realm) { PasswordForm form2(form); - form2.origin = GURL(signon_realm); + form2.url = GURL(signon_realm); form2.action = GURL(signon_realm); form2.signon_realm = signon_realm; return form2; @@ -840,7 +840,7 @@ TEST_F(LoginDatabaseTest, TestPublicSuffixDomainMatchingRegexp) { // Example password form. PasswordForm form; - form.origin = GURL("http://foo.com/"); + form.url = GURL("http://foo.com/"); form.action = GURL("http://foo.com/login"); form.username_element = ASCIIToUTF16("username"); form.username_value = ASCIIToUTF16("test@gmail.com"); @@ -946,7 +946,7 @@ static bool AddTimestampedLogin(LoginDatabase* db, bool date_is_creation) { // Example password form. PasswordForm form; - form.origin = GURL(url + std::string("/LoginAuth")); + form.url = GURL(url + std::string("/LoginAuth")); form.username_element = ASCIIToUTF16(unique_string); form.username_value = ASCIIToUTF16(unique_string); form.password_element = ASCIIToUTF16(unique_string); @@ -1083,7 +1083,7 @@ TEST_F(LoginDatabaseTest, DisableAutoSignInForOrigin) { EXPECT_TRUE(db().DisableAutoSignInForOrigin(origin3)); EXPECT_TRUE(db().GetAutofillableLogins(&result)); for (const auto& form : result) { - if (form->origin == origin1 || form->origin == origin3) + if (form->url == origin1 || form->url == origin3) EXPECT_TRUE(form->skip_zero_click); else EXPECT_FALSE(form->skip_zero_click); @@ -1099,7 +1099,7 @@ TEST_F(LoginDatabaseTest, BlacklistedLogins) { // Save a form as blacklisted. PasswordForm form; - form.origin = GURL("http://accounts.google.com/LoginAuth"); + form.url = GURL("http://accounts.google.com/LoginAuth"); form.action = GURL("http://accounts.google.com/Login"); form.username_element = ASCIIToUTF16("Email"); form.password_element = ASCIIToUTF16("Passwd"); @@ -1181,7 +1181,7 @@ TEST_F(LoginDatabaseTest, UpdateIncompleteCredentials) { // are sometimes inserted during import from other browsers (which may not // store this info). PasswordForm incomplete_form; - incomplete_form.origin = GURL("http://accounts.google.com/LoginAuth"); + incomplete_form.url = GURL("http://accounts.google.com/LoginAuth"); incomplete_form.signon_realm = "http://accounts.google.com/"; incomplete_form.username_value = ASCIIToUTF16("my_username"); incomplete_form.password_value = ASCIIToUTF16("my_password"); @@ -1192,7 +1192,7 @@ TEST_F(LoginDatabaseTest, UpdateIncompleteCredentials) { // A form on some website. It should trigger a match with the stored one. PasswordForm encountered_form; - encountered_form.origin = GURL("http://accounts.google.com/LoginAuth"); + encountered_form.url = GURL("http://accounts.google.com/LoginAuth"); encountered_form.signon_realm = "http://accounts.google.com/"; encountered_form.action = GURL("http://accounts.google.com/Login"); encountered_form.username_element = ASCIIToUTF16("Email"); @@ -1203,7 +1203,7 @@ TEST_F(LoginDatabaseTest, UpdateIncompleteCredentials) { EXPECT_TRUE( db().GetLogins(PasswordStore::FormDigest(encountered_form), &result)); ASSERT_EQ(1U, result.size()); - EXPECT_EQ(incomplete_form.origin, result[0]->origin); + EXPECT_EQ(incomplete_form.url, result[0]->url); EXPECT_EQ(incomplete_form.signon_realm, result[0]->signon_realm); EXPECT_EQ(incomplete_form.username_value, result[0]->username_value); EXPECT_EQ(incomplete_form.password_value, result[0]->password_value); @@ -1248,7 +1248,7 @@ TEST_F(LoginDatabaseTest, UpdateOverlappingCredentials) { // are sometimes inserted during import from other browsers (which may not // store this info). PasswordForm incomplete_form; - incomplete_form.origin = GURL("http://accounts.google.com/LoginAuth"); + incomplete_form.url = GURL("http://accounts.google.com/LoginAuth"); incomplete_form.signon_realm = "http://accounts.google.com/"; incomplete_form.username_value = ASCIIToUTF16("my_username"); incomplete_form.password_value = ASCIIToUTF16("my_password"); @@ -1298,7 +1298,7 @@ TEST_F(LoginDatabaseTest, UpdateOverlappingCredentials) { TEST_F(LoginDatabaseTest, DoubleAdd) { PasswordForm form; - form.origin = GURL("http://accounts.google.com/LoginAuth"); + form.url = GURL("http://accounts.google.com/LoginAuth"); form.signon_realm = "http://accounts.google.com/"; form.username_value = ASCIIToUTF16("my_username"); form.password_value = ASCIIToUTF16("my_password"); @@ -1317,7 +1317,7 @@ TEST_F(LoginDatabaseTest, DoubleAdd) { TEST_F(LoginDatabaseTest, AddWrongForm) { PasswordForm form; // |origin| shouldn't be empty. - form.origin = GURL(); + form.url = GURL(); form.signon_realm = "http://accounts.google.com/"; form.username_value = ASCIIToUTF16("my_username"); form.password_value = ASCIIToUTF16("my_password"); @@ -1326,14 +1326,14 @@ TEST_F(LoginDatabaseTest, AddWrongForm) { EXPECT_EQ(PasswordStoreChangeList(), db().AddLogin(form)); // |signon_realm| shouldn't be empty. - form.origin = GURL("http://accounts.google.com/LoginAuth"); + form.url = GURL("http://accounts.google.com/LoginAuth"); form.signon_realm.clear(); EXPECT_EQ(PasswordStoreChangeList(), db().AddLogin(form)); } TEST_F(LoginDatabaseTest, UpdateLogin) { PasswordForm form; - form.origin = GURL("http://accounts.google.com/LoginAuth"); + form.url = GURL("http://accounts.google.com/LoginAuth"); form.signon_realm = "http://accounts.google.com/"; form.username_value = ASCIIToUTF16("my_username"); form.password_value = ASCIIToUTF16("my_password"); @@ -1375,10 +1375,48 @@ TEST_F(LoginDatabaseTest, UpdateLogin) { EXPECT_EQ(form, *result[0]); } +TEST_F(LoginDatabaseTest, UpdateLoginWithoutPassword) { + PasswordForm form; + form.url = GURL("http://accounts.google.com/LoginAuth"); + form.signon_realm = "http://accounts.google.com/"; + form.username_value = ASCIIToUTF16("my_username"); + form.password_value = ASCIIToUTF16("my_password"); + form.blacklisted_by_user = false; + form.scheme = PasswordForm::Scheme::kHtml; + form.date_last_used = base::Time::Now(); + EXPECT_EQ(AddChangeForForm(form), db().AddLogin(form)); + + form.action = GURL("http://accounts.google.com/login"); + form.all_possible_usernames.push_back(autofill::ValueElementPair( + ASCIIToUTF16("my_new_username"), ASCIIToUTF16("new_username_id"))); + form.times_used = 20; + form.submit_element = ASCIIToUTF16("submit_element"); + form.date_synced = base::Time::Now(); + form.date_created = base::Time::Now() - base::TimeDelta::FromDays(1); + form.date_last_used = base::Time::Now() + base::TimeDelta::FromDays(1); + form.display_name = ASCIIToUTF16("Mr. Smith"); + form.icon_url = GURL("https://accounts.google.com/Icon"); + form.skip_zero_click = true; + form.moving_blocked_for_list.push_back(GaiaIdHash::FromGaiaId("gaia_id")); + + PasswordStoreChangeList changes = db().UpdateLogin(form); + EXPECT_EQ(UpdateChangeForForm(form, /*passwordchanged=*/false), changes); + ASSERT_EQ(1U, changes.size()); + EXPECT_EQ(1, changes[0].primary_key()); + + // When we retrieve the form from the store, it should have |in_store| set. + form.in_store = PasswordForm::Store::kProfileStore; + + std::vector<std::unique_ptr<PasswordForm>> result; + ASSERT_TRUE(db().GetLogins(PasswordStore::FormDigest(form), &result)); + ASSERT_EQ(1U, result.size()); + EXPECT_EQ(form, *result[0]); +} + TEST_F(LoginDatabaseTest, RemoveWrongForm) { PasswordForm form; // |origin| shouldn't be empty. - form.origin = GURL("http://accounts.google.com/LoginAuth"); + form.url = GURL("http://accounts.google.com/LoginAuth"); form.signon_realm = "http://accounts.google.com/"; form.username_value = ASCIIToUTF16("my_username"); form.password_value = ASCIIToUTF16("my_password"); @@ -1396,7 +1434,7 @@ namespace { void AddMetricsTestData(LoginDatabase* db) { PasswordForm password_form; - password_form.origin = GURL("http://example.com"); + password_form.url = GURL("http://example.com"); password_form.username_value = ASCIIToUTF16("test1@gmail.com"); password_form.password_value = ASCIIToUTF16("test"); password_form.signon_realm = "http://example.com/"; @@ -1407,7 +1445,7 @@ void AddMetricsTestData(LoginDatabase* db) { password_form.times_used = 1; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("http://second.example.com"); + password_form.url = GURL("http://second.example.com"); password_form.signon_realm = "http://second.example.com"; password_form.times_used = 3; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); @@ -1417,13 +1455,13 @@ void AddMetricsTestData(LoginDatabase* db) { password_form.times_used = 2; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("ftp://third.example.com/"); + password_form.url = GURL("ftp://third.example.com/"); password_form.signon_realm = "ftp://third.example.com/"; password_form.times_used = 4; password_form.scheme = PasswordForm::Scheme::kOther; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("http://fourth.example.com/"); + password_form.url = GURL("http://fourth.example.com/"); password_form.signon_realm = "http://fourth.example.com/"; password_form.type = PasswordForm::Type::kManual; password_form.username_value = ASCIIToUTF16(""); @@ -1431,20 +1469,20 @@ void AddMetricsTestData(LoginDatabase* db) { password_form.scheme = PasswordForm::Scheme::kHtml; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("https://fifth.example.com/"); + password_form.url = GURL("https://fifth.example.com/"); password_form.signon_realm = "https://fifth.example.com/"; password_form.password_value = ASCIIToUTF16(""); password_form.blacklisted_by_user = true; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("https://sixth.example.com/"); + password_form.url = GURL("https://sixth.example.com/"); password_form.signon_realm = "https://sixth.example.com/"; password_form.username_value = ASCIIToUTF16("my_username"); password_form.password_value = ASCIIToUTF16("my_password"); password_form.blacklisted_by_user = false; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL(); + password_form.url = GURL(); password_form.signon_realm = "android://hash@com.example.android/"; password_form.username_value = ASCIIToUTF16("JohnDoe"); password_form.password_value = ASCIIToUTF16("my_password"); @@ -1454,22 +1492,22 @@ void AddMetricsTestData(LoginDatabase* db) { password_form.username_value = ASCIIToUTF16("JaneDoe"); EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("http://rsolomakhin.github.io/autofill/"); + password_form.url = GURL("http://rsolomakhin.github.io/autofill/"); password_form.signon_realm = "http://rsolomakhin.github.io/"; password_form.blacklisted_by_user = true; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("https://rsolomakhin.github.io/autofill/"); + password_form.url = GURL("https://rsolomakhin.github.io/autofill/"); password_form.signon_realm = "https://rsolomakhin.github.io/"; password_form.blacklisted_by_user = true; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("http://rsolomakhin.github.io/autofill/123"); + password_form.url = GURL("http://rsolomakhin.github.io/autofill/123"); password_form.signon_realm = "http://rsolomakhin.github.io/"; password_form.blacklisted_by_user = true; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); - password_form.origin = GURL("https://rsolomakhin.github.io/autofill/1234"); + password_form.url = GURL("https://rsolomakhin.github.io/autofill/1234"); password_form.signon_realm = "https://rsolomakhin.github.io/"; password_form.blacklisted_by_user = true; EXPECT_EQ(AddChangeForForm(password_form), db->AddLogin(password_form)); @@ -1662,8 +1700,6 @@ TEST_F(LoginDatabaseTest, ReportAccountStoreMetricsTest) { "WithoutCustomPassphrase", 9, 1); -#if 0 - // TODO(crbug.com/1063852): Add recording code for the scheme-based metrics. histogram_tester.ExpectUniqueSample( "PasswordManager.AccountStore.TotalAccountsHiRes.WithScheme.Android", 2, 1); @@ -1675,7 +1711,6 @@ TEST_F(LoginDatabaseTest, ReportAccountStoreMetricsTest) { "PasswordManager.AccountStore.TotalAccountsHiRes.WithScheme.Https", 1, 1); histogram_tester.ExpectUniqueSample( "PasswordManager.AccountStore.TotalAccountsHiRes.WithScheme.Other", 0, 1); -#endif histogram_tester.ExpectBucketCount( "PasswordManager.AccountStore.TimesPasswordUsed.AutoGenerated." @@ -1716,13 +1751,18 @@ TEST_F(LoginDatabaseTest, ReportAccountStoreMetricsTest) { "PasswordManager.AccountStore.TimesPasswordUsed.Overall." "WithoutCustomPassphrase", 3, 2); + + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStore.EmptyUsernames.CountInDatabase", 1, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStore.InaccessiblePasswords", 0, 1); } TEST_F(LoginDatabaseTest, DuplicatesMetrics_NoDuplicates) { // No duplicate. PasswordForm password_form; password_form.signon_realm = "http://example1.com/"; - password_form.origin = GURL("http://example1.com/"); + password_form.url = GURL("http://example1.com/"); password_form.username_element = ASCIIToUTF16("userelem_1"); password_form.username_value = ASCIIToUTF16("username_1"); password_form.password_value = ASCIIToUTF16("password_1"); @@ -1730,7 +1770,7 @@ TEST_F(LoginDatabaseTest, DuplicatesMetrics_NoDuplicates) { // Different username -> no duplicate. password_form.signon_realm = "http://example2.com/"; - password_form.origin = GURL("http://example2.com/"); + password_form.url = GURL("http://example2.com/"); password_form.username_value = ASCIIToUTF16("username_1"); ASSERT_EQ(AddChangeForForm(password_form), db().AddLogin(password_form)); password_form.username_value = ASCIIToUTF16("username_2"); @@ -1739,7 +1779,7 @@ TEST_F(LoginDatabaseTest, DuplicatesMetrics_NoDuplicates) { // Blacklisted forms don't count as duplicates (neither against other // blacklisted forms nor against actual saved credentials). password_form.signon_realm = "http://example3.com/"; - password_form.origin = GURL("http://example3.com/"); + password_form.url = GURL("http://example3.com/"); password_form.username_value = ASCIIToUTF16("username_1"); ASSERT_EQ(AddChangeForForm(password_form), db().AddLogin(password_form)); password_form.blacklisted_by_user = true; @@ -1764,7 +1804,7 @@ TEST_F(LoginDatabaseTest, DuplicatesMetrics_ExactDuplicates) { // username_element is different, which doesn't matter). PasswordForm password_form; password_form.signon_realm = "http://example1.com/"; - password_form.origin = GURL("http://example1.com/"); + password_form.url = GURL("http://example1.com/"); password_form.username_element = ASCIIToUTF16("userelem_1"); password_form.username_value = ASCIIToUTF16("username_1"); ASSERT_EQ(AddChangeForForm(password_form), db().AddLogin(password_form)); @@ -1777,9 +1817,9 @@ TEST_F(LoginDatabaseTest, DuplicatesMetrics_ExactDuplicates) { // Similarly, origin doesn't make forms "different" either. password_form.signon_realm = "http://example2.com/"; - password_form.origin = GURL("http://example2.com/path1"); + password_form.url = GURL("http://example2.com/path1"); ASSERT_EQ(AddChangeForForm(password_form), db().AddLogin(password_form)); - password_form.origin = GURL("http://example2.com/path2"); + password_form.url = GURL("http://example2.com/path2"); ASSERT_EQ(AddChangeForForm(password_form), db().AddLogin(password_form)); base::HistogramTester histogram_tester; @@ -1798,7 +1838,7 @@ TEST_F(LoginDatabaseTest, DuplicatesMetrics_MismatchedDuplicates) { // Mismatched duplicates: Identical except for the password. PasswordForm password_form; password_form.signon_realm = "http://example1.com/"; - password_form.origin = GURL("http://example1.com/"); + password_form.url = GURL("http://example1.com/"); password_form.username_element = ASCIIToUTF16("userelem_1"); password_form.username_value = ASCIIToUTF16("username_1"); password_form.password_element = ASCIIToUTF16("passelem_1"); @@ -2280,7 +2320,7 @@ INSTANTIATE_TEST_SUITE_P(MigrationToVCurrent, class LoginDatabaseUndecryptableLoginsTest : public testing::Test { protected: - LoginDatabaseUndecryptableLoginsTest() {} + LoginDatabaseUndecryptableLoginsTest() = default; void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); @@ -2321,7 +2361,7 @@ PasswordForm LoginDatabaseUndecryptableLoginsTest::AddDummyLogin( // Create a dummy password form. const base::string16 unique_string16 = ASCIIToUTF16(unique_string); PasswordForm form; - form.origin = origin; + form.url = origin; form.username_element = unique_string16; form.username_value = unique_string16; form.password_element = unique_string16; @@ -2414,7 +2454,6 @@ TEST_F(LoginDatabaseUndecryptableLoginsTest, DeleteUndecryptableLoginsTest) { #if defined(OS_MACOSX) && !defined(OS_IOS) TEST_F(LoginDatabaseUndecryptableLoginsTest, PasswordRecoveryEnabledGetLogins) { - base::HistogramTester histogram_tester; base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(features::kDeleteCorruptedPasswords); @@ -2436,18 +2475,10 @@ TEST_F(LoginDatabaseUndecryptableLoginsTest, PasswordRecoveryEnabledGetLogins) { RunUntilIdle(); EXPECT_TRUE(testing_local_state().HasPrefPath(prefs::kPasswordRecovery)); - - histogram_tester.ExpectUniqueSample( - "PasswordManager.RemovedCorruptedPasswords", 1, 1); - histogram_tester.ExpectUniqueSample( - "PasswordManager.DeleteCorruptedPasswordsResult", - metrics_util::DeleteCorruptedPasswordsResult::kSuccessPasswordsDeleted, - 1); } TEST_F(LoginDatabaseUndecryptableLoginsTest, PasswordRecoveryDisabledGetLogins) { - base::HistogramTester histogram_tester; base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndDisableFeature( features::kDeleteCorruptedPasswords); @@ -2469,19 +2500,10 @@ TEST_F(LoginDatabaseUndecryptableLoginsTest, RunUntilIdle(); EXPECT_FALSE(testing_local_state().HasPrefPath(prefs::kPasswordRecovery)); - - EXPECT_TRUE(histogram_tester - .GetAllSamples("PasswordManager.RemovedCorruptedPasswords") - .empty()); - EXPECT_TRUE( - histogram_tester - .GetAllSamples("PasswordManager.DeleteCorruptedPasswordsResult") - .empty()); } TEST_F(LoginDatabaseUndecryptableLoginsTest, PasswordRecoveryEnabledKeychainLocked) { - base::HistogramTester histogram_tester; base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(features::kDeleteCorruptedPasswords); @@ -2505,14 +2527,6 @@ TEST_F(LoginDatabaseUndecryptableLoginsTest, RunUntilIdle(); EXPECT_FALSE(testing_local_state().HasPrefPath(prefs::kPasswordRecovery)); - EXPECT_TRUE(histogram_tester - .GetAllSamples("PasswordManager.RemovedCorruptedPasswords") - .empty()); - EXPECT_TRUE( - histogram_tester - .GetAllSamples("PasswordManager.DeleteCorruptedPasswordsResult") - .empty()); - // Note: it's not possible that encryption suddenly becomes available. This is // only used to check that the form is not removed from the database. OSCryptMocker::SetBackendLocked(false); @@ -2564,8 +2578,8 @@ TEST_F(LoginDatabaseTest, GetLoginsByPassword) { // Insert another form with a different password for a different origin. PasswordForm form2; GenerateExamplePasswordForm(&form2); - form2.origin = GURL("https://myrandomsite.com/login.php"); - form2.signon_realm = form2.origin.GetOrigin().spec(); + form2.url = GURL("https://myrandomsite.com/login.php"); + form2.signon_realm = form2.url.GetOrigin().spec(); form2.password_value = base::ASCIIToUTF16("my-unique-random-password"); changes = db().AddLogin(form2); ASSERT_EQ(AddChangeForForm(form2), changes); @@ -2577,8 +2591,8 @@ TEST_F(LoginDatabaseTest, GetLoginsByPassword) { // Insert another form with the target password for a different origin. PasswordForm form3; GenerateExamplePasswordForm(&form3); - form3.origin = GURL("https://myrandomsite1.com/login.php"); - form3.signon_realm = form3.origin.GetOrigin().spec(); + form3.url = GURL("https://myrandomsite1.com/login.php"); + form3.signon_realm = form3.url.GetOrigin().spec(); form3.password_value = duplicated_password; changes = db().AddLogin(form3); ASSERT_EQ(AddChangeForForm(form3), changes); @@ -2591,7 +2605,7 @@ TEST_F(LoginDatabaseTest, GetLoginsByPassword) { // Test encrypted passwords are present in add change lists. TEST_F(LoginDatabaseTest, EncryptedPasswordAdd) { PasswordForm form; - form.origin = GURL("http://0.com"); + form.url = GURL("http://0.com"); form.signon_realm = "http://www.example.com/"; form.action = GURL("http://www.example.com/action"); form.password_element = base::ASCIIToUTF16("pwd"); @@ -2605,7 +2619,7 @@ TEST_F(LoginDatabaseTest, EncryptedPasswordAdd) { // is already in the DB. TEST_F(LoginDatabaseTest, EncryptedPasswordAddWithReplaceSemantics) { PasswordForm form; - form.origin = GURL("http://0.com"); + form.url = GURL("http://0.com"); form.signon_realm = "http://www.example.com/"; form.action = GURL("http://www.example.com/action"); form.password_element = base::ASCIIToUTF16("pwd"); @@ -2625,7 +2639,7 @@ TEST_F(LoginDatabaseTest, EncryptedPasswordAddWithReplaceSemantics) { // Test encrypted passwords are present in update change lists. TEST_F(LoginDatabaseTest, EncryptedPasswordUpdate) { PasswordForm form; - form.origin = GURL("http://0.com"); + form.url = GURL("http://0.com"); form.signon_realm = "http://www.example.com/"; form.action = GURL("http://www.example.com/action"); form.password_element = base::ASCIIToUTF16("pwd"); @@ -2643,7 +2657,7 @@ TEST_F(LoginDatabaseTest, EncryptedPasswordUpdate) { // Test encrypted passwords are present when retrieving from DB. TEST_F(LoginDatabaseTest, GetLoginsEncryptedPassword) { PasswordForm form; - form.origin = GURL("http://0.com"); + form.url = GURL("http://0.com"); form.signon_realm = "http://www.example.com/"; form.action = GURL("http://www.example.com/action"); form.password_element = base::ASCIIToUTF16("pwd"); diff --git a/chromium/components/password_manager/core/browser/mock_bulk_leak_check_service.cc b/chromium/components/password_manager/core/browser/mock_bulk_leak_check_service.cc new file mode 100644 index 00000000000..b569ac3eb62 --- /dev/null +++ b/chromium/components/password_manager/core/browser/mock_bulk_leak_check_service.cc @@ -0,0 +1,14 @@ +// Copyright 2020 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/mock_bulk_leak_check_service.h" + +#include "base/timer/elapsed_timer.h" + +namespace password_manager { + +MockBulkLeakCheckService::MockBulkLeakCheckService() = default; +MockBulkLeakCheckService::~MockBulkLeakCheckService() = default; + +} // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/mock_bulk_leak_check_service.h b/chromium/components/password_manager/core/browser/mock_bulk_leak_check_service.h new file mode 100644 index 00000000000..5e3a702aa33 --- /dev/null +++ b/chromium/components/password_manager/core/browser/mock_bulk_leak_check_service.h @@ -0,0 +1,38 @@ +// Copyright 2020 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. + +#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_BULK_LEAK_CHECK_SERVICE_H_ +#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_BULK_LEAK_CHECK_SERVICE_H_ + +#include <memory> +#include <vector> + +#include "base/memory/scoped_refptr.h" +#include "base/observer_list.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/password_manager/core/browser/bulk_leak_check_service_interface.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace password_manager { + +// Mocked BulkLeakCheckService used by unit tests. +class MockBulkLeakCheckService : public BulkLeakCheckServiceInterface { + public: + MockBulkLeakCheckService(); + ~MockBulkLeakCheckService() override; + MOCK_METHOD(void, + CheckUsernamePasswordPairs, + (std::vector<LeakCheckCredential>), + (override)); + MOCK_METHOD(void, Cancel, (), (override)); + MOCK_METHOD(size_t, GetPendingChecksCount, (), (const, override)); + MOCK_METHOD(State, GetState, (), (const, override)); + MOCK_METHOD(void, AddObserver, (Observer*), (override)); + MOCK_METHOD(void, RemoveObserver, (Observer*), (override)); + MOCK_METHOD(void, Shutdown, (), (override)); +}; + +} // namespace password_manager + +#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_MOCK_BULK_LEAK_CHECK_SERVICE_H_ diff --git a/chromium/components/password_manager/core/browser/mock_password_feature_manager.cc b/chromium/components/password_manager/core/browser/mock_password_feature_manager.cc index 1cc00d6b00f..51710b47a83 100644 --- a/chromium/components/password_manager/core/browser/mock_password_feature_manager.cc +++ b/chromium/components/password_manager/core/browser/mock_password_feature_manager.cc @@ -6,7 +6,7 @@ namespace password_manager { -MockPasswordFeatureManager::MockPasswordFeatureManager() {} +MockPasswordFeatureManager::MockPasswordFeatureManager() = default; MockPasswordFeatureManager::~MockPasswordFeatureManager() = default; diff --git a/chromium/components/password_manager/core/browser/mock_password_feature_manager.h b/chromium/components/password_manager/core/browser/mock_password_feature_manager.h index b11fd285557..4ec7a46ee02 100644 --- a/chromium/components/password_manager/core/browser/mock_password_feature_manager.h +++ b/chromium/components/password_manager/core/browser/mock_password_feature_manager.h @@ -23,7 +23,7 @@ class MockPasswordFeatureManager : public PasswordFeatureManager { MOCK_METHOD0(OptInToAccountStorage, void()); MOCK_METHOD0(OptOutOfAccountStorageAndClearSettings, void()); - MOCK_CONST_METHOD0(ShouldShowPasswordStorePicker, bool()); + MOCK_CONST_METHOD0(ShouldShowAccountStorageBubbleUi, bool()); MOCK_METHOD1(SetDefaultPasswordStore, void(const autofill::PasswordForm::Store& store)); diff --git a/chromium/components/password_manager/core/browser/mock_password_form_manager_for_ui.h b/chromium/components/password_manager/core/browser/mock_password_form_manager_for_ui.h index ce857c4cf31..ff1fa8718ae 100644 --- a/chromium/components/password_manager/core/browser/mock_password_form_manager_for_ui.h +++ b/chromium/components/password_manager/core/browser/mock_password_form_manager_for_ui.h @@ -18,7 +18,7 @@ class MockPasswordFormManagerForUI : public PasswordFormManagerForUI { MockPasswordFormManagerForUI(); ~MockPasswordFormManagerForUI() override; - MOCK_METHOD(const GURL&, GetOrigin, (), (const override)); + MOCK_METHOD(const GURL&, GetURL, (), (const override)); MOCK_METHOD(const std::vector<const autofill::PasswordForm*>&, GetBestMatches, (), @@ -45,6 +45,7 @@ class MockPasswordFormManagerForUI : public PasswordFormManagerForUI { (), (const override)); MOCK_METHOD(bool, IsBlacklisted, (), (const override)); + MOCK_METHOD(bool, WasUnblacklisted, (), (const override)); MOCK_METHOD(bool, IsMovableToAccountStore, (), (const override)); MOCK_METHOD(void, Save, (), (override)); MOCK_METHOD(void, Update, (const autofill::PasswordForm&), (override)); diff --git a/chromium/components/password_manager/core/browser/multi_store_form_fetcher.cc b/chromium/components/password_manager/core/browser/multi_store_form_fetcher.cc index 7e86b0ef914..33b2da12225 100644 --- a/chromium/components/password_manager/core/browser/multi_store_form_fetcher.cc +++ b/chromium/components/password_manager/core/browser/multi_store_form_fetcher.cc @@ -38,20 +38,23 @@ void MultiStoreFormFetcher::Fetch() { need_to_refetch_ = true; return; } - // Issue a fetch from the profile password store using the base class - // FormFetcherImpl. - FormFetcherImpl::Fetch(); - if (state_ == State::WAITING) { - // Fetching from the profile password store is in progress. - wait_counter_++; - } - // Issue a fetch from the account password store if available. PasswordStore* account_password_store = client_->GetAccountPasswordStore(); + + // Issue a fetch from the profile store and, if it exists, also from the + // account store. + // Set up |wait_counter_| *before* triggering any of the fetches. This ensures + // that things work correctly (i.e. we don't notify of completion too early) + // even if the fetches return synchronously (which is the case in tests). + wait_counter_++; + if (account_password_store) + wait_counter_++; + + // Let the base class handle the fetch from the profile store. + FormFetcherImpl::Fetch(); if (account_password_store) { - account_password_store->GetLogins(form_digest_, this); state_ = State::WAITING; - wait_counter_++; + account_password_store->GetLogins(form_digest_, this); } } @@ -98,9 +101,34 @@ std::unique_ptr<FormFetcher> MultiStoreFormFetcher::Clone() { void MultiStoreFormFetcher::OnGetPasswordStoreResults( std::vector<std::unique_ptr<PasswordForm>> results) { + // This class overrides OnGetPasswordStoreResultsFrom() (the version of this + // method that also receives the originating store), so the store-less version + // never gets called. + NOTREACHED(); +} + +void MultiStoreFormFetcher::OnGetPasswordStoreResultsFrom( + scoped_refptr<PasswordStore> store, + std::vector<std::unique_ptr<PasswordForm>> results) { DCHECK_EQ(State::WAITING, state_); DCHECK_GT(wait_counter_, 0); + if (store.get() == client_->GetProfilePasswordStore() && + should_migrate_http_passwords_ && results.empty() && + form_digest_.url.SchemeIs(url::kHttpsScheme)) { + // TODO(crbug.com/1095556): Consider also supporting HTTP->HTTPS migration + // for the account store. + http_migrator_ = std::make_unique<HttpPasswordStoreMigrator>( + url::Origin::Create(form_digest_.url), client_, this); + // The migrator will call us back at ProcessMigratedForms(). + return; + } + + AggregatePasswordStoreResults(std::move(results)); +} + +void MultiStoreFormFetcher::AggregatePasswordStoreResults( + std::vector<std::unique_ptr<PasswordForm>> results) { // Store the results. for (auto& form : results) partial_results_.push_back(std::move(form)); @@ -125,6 +153,13 @@ void MultiStoreFormFetcher::OnGetPasswordStoreResults( ProcessPasswordStoreResults(std::move(partial_results_)); } +void MultiStoreFormFetcher::ProcessMigratedForms( + std::vector<std::unique_ptr<PasswordForm>> forms) { + // The migration from HTTP to HTTPS (within the profile store) was finished. + // Continue processing with the migrated results. + AggregatePasswordStoreResults(std::move(forms)); +} + void MultiStoreFormFetcher::SplitResults( std::vector<std::unique_ptr<PasswordForm>> results) { // Compute the |is_blacklisted_in_profile_store_| and diff --git a/chromium/components/password_manager/core/browser/multi_store_form_fetcher.h b/chromium/components/password_manager/core/browser/multi_store_form_fetcher.h index 1568bfb55ba..5402e6dcadc 100644 --- a/chromium/components/password_manager/core/browser/multi_store_form_fetcher.h +++ b/chromium/components/password_manager/core/browser/multi_store_form_fetcher.h @@ -31,8 +31,18 @@ class MultiStoreFormFetcher : public FormFetcherImpl { // PasswordStoreConsumer: void OnGetPasswordStoreResults( std::vector<std::unique_ptr<autofill::PasswordForm>> results) override; + void OnGetPasswordStoreResultsFrom( + scoped_refptr<PasswordStore> store, + std::vector<std::unique_ptr<autofill::PasswordForm>> results) override; private: + // HttpPasswordStoreMigrator::Consumer: + void ProcessMigratedForms( + std::vector<std::unique_ptr<autofill::PasswordForm>> forms) override; + + void AggregatePasswordStoreResults( + std::vector<std::unique_ptr<autofill::PasswordForm>> results); + // Splits |results| into |federated_|, |non_federated_|, // |is_blacklisted_in_profile_store_| and |is_blacklisted_in_account_store_|. void SplitResults( diff --git a/chromium/components/password_manager/core/browser/multi_store_form_fetcher_unittest.cc b/chromium/components/password_manager/core/browser/multi_store_form_fetcher_unittest.cc index aa0121aeaa8..8698a1d48eb 100644 --- a/chromium/components/password_manager/core/browser/multi_store_form_fetcher_unittest.cc +++ b/chromium/components/password_manager/core/browser/multi_store_form_fetcher_unittest.cc @@ -74,7 +74,7 @@ PasswordForm CreateHTMLForm(const std::string& origin_url, base::Time date_last_used = base::Time::Now()) { PasswordForm form; form.scheme = PasswordForm::Scheme::kHtml; - form.origin = GURL(origin_url); + form.url = GURL(origin_url); form.signon_realm = origin_url; form.username_value = ASCIIToUTF16(username_value); form.password_value = ASCIIToUTF16(password_value); @@ -201,8 +201,9 @@ TEST_F(MultiStoreFormFetcherTest, CloningMultiStoreFetcherClonesState) { blacklisted.in_store = PasswordForm::Store::kAccountStore; std::vector<std::unique_ptr<PasswordForm>> results; results.push_back(std::make_unique<PasswordForm>(blacklisted)); - form_fetcher_->OnGetPasswordStoreResults(std::move(results)); - form_fetcher_->OnGetPasswordStoreResults({}); + form_fetcher_->OnGetPasswordStoreResultsFrom(account_mock_store_, + std::move(results)); + form_fetcher_->OnGetPasswordStoreResultsFrom(profile_mock_store_, {}); EXPECT_EQ(form_fetcher_->GetState(), FormFetcher::State::NOT_WAITING); EXPECT_TRUE(form_fetcher_->IsBlacklisted()); @@ -234,8 +235,9 @@ TEST_F(MultiStoreFormFetcherTest, CloningMultiStoreFetcherResumesFetch) { blacklisted.in_store = PasswordForm::Store::kAccountStore; std::vector<std::unique_ptr<PasswordForm>> results; results.push_back(std::make_unique<PasswordForm>(blacklisted)); - form_fetcher_->OnGetPasswordStoreResults(std::move(results)); - form_fetcher_->OnGetPasswordStoreResults({}); + form_fetcher_->OnGetPasswordStoreResultsFrom(account_mock_store_, + std::move(results)); + form_fetcher_->OnGetPasswordStoreResultsFrom(profile_mock_store_, {}); EXPECT_EQ(form_fetcher_->GetState(), FormFetcher::State::NOT_WAITING); EXPECT_TRUE(form_fetcher_->IsBlacklisted()); @@ -247,12 +249,12 @@ TEST_F(MultiStoreFormFetcherTest, Empty) { form_fetcher_->AddConsumer(&consumer_); EXPECT_CALL(consumer_, OnFetchCompleted); // Both profile and account respond with empty results. - form_fetcher_->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + form_fetcher_->OnGetPasswordStoreResultsFrom( + profile_mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); // We should be still waiting for the second store to respond. EXPECT_EQ(FormFetcher::State::WAITING, form_fetcher_->GetState()); - form_fetcher_->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + form_fetcher_->OnGetPasswordStoreResultsFrom( + account_mock_store_, std::vector<std::unique_ptr<PasswordForm>>()); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); EXPECT_THAT(form_fetcher_->GetNonFederatedMatches(), IsEmpty()); EXPECT_THAT(form_fetcher_->GetFederatedMatches(), IsEmpty()); @@ -282,7 +284,8 @@ TEST_F(MultiStoreFormFetcherTest, MergeFromBothStores) { results.push_back(std::make_unique<PasswordForm>(federated2)); results.push_back(std::make_unique<PasswordForm>(non_federated1)); results.push_back(std::make_unique<PasswordForm>(blacklisted)); - form_fetcher_->OnGetPasswordStoreResults(std::move(results)); + form_fetcher_->OnGetPasswordStoreResultsFrom(profile_mock_store_, + std::move(results)); // We should be still waiting for the second store to respond. EXPECT_EQ(FormFetcher::State::WAITING, form_fetcher_->GetState()); @@ -294,7 +297,8 @@ TEST_F(MultiStoreFormFetcherTest, MergeFromBothStores) { results.push_back(std::make_unique<PasswordForm>(non_federated3)); EXPECT_CALL(consumer_, OnFetchCompleted); - form_fetcher_->OnGetPasswordStoreResults(std::move(results)); + form_fetcher_->OnGetPasswordStoreResultsFrom(account_mock_store_, + std::move(results)); EXPECT_EQ(FormFetcher::State::NOT_WAITING, form_fetcher_->GetState()); @@ -318,9 +322,10 @@ TEST_F(MultiStoreFormFetcherTest, BlacklistEntryInTheAccountStore) { // Pass response from the first store. std::vector<std::unique_ptr<PasswordForm>> results; results.push_back(std::make_unique<PasswordForm>(blacklisted)); - form_fetcher_->OnGetPasswordStoreResults(std::move(results)); + form_fetcher_->OnGetPasswordStoreResultsFrom(account_mock_store_, + std::move(results)); // Pass empty response from the second store. - form_fetcher_->OnGetPasswordStoreResults({}); + form_fetcher_->OnGetPasswordStoreResultsFrom(profile_mock_store_, {}); // Simulate a user in the account mode. ON_CALL(*client()->GetPasswordFeatureManager(), IsOptedInForAccountStorage()) @@ -357,9 +362,10 @@ TEST_F(MultiStoreFormFetcherTest, BlacklistEntryInTheProfileStore) { // Pass response from the first store. std::vector<std::unique_ptr<PasswordForm>> results; results.push_back(std::make_unique<PasswordForm>(blacklisted)); - form_fetcher_->OnGetPasswordStoreResults(std::move(results)); + form_fetcher_->OnGetPasswordStoreResultsFrom(profile_mock_store_, + std::move(results)); // Pass empty response from the second store. - form_fetcher_->OnGetPasswordStoreResults({}); + form_fetcher_->OnGetPasswordStoreResultsFrom(account_mock_store_, {}); // Simulate a user in the account mode. ON_CALL(*client()->GetPasswordFeatureManager(), IsOptedInForAccountStorage()) @@ -415,9 +421,10 @@ TEST_F(MultiStoreFormFetcherTest, MovingToAccountStoreIsBlocked) { results.push_back(std::make_unique<PasswordForm>(blocked_form)); results.push_back(std::make_unique<PasswordForm>(unblocked_form)); results.push_back(std::make_unique<PasswordForm>(psl_form)); - form_fetcher_->OnGetPasswordStoreResults(std::move(results)); + form_fetcher_->OnGetPasswordStoreResultsFrom(profile_mock_store_, + std::move(results)); // Pass empty response from the account store. - form_fetcher_->OnGetPasswordStoreResults({}); + form_fetcher_->OnGetPasswordStoreResultsFrom(account_mock_store_, {}); // Moving should be blocked for |kUser| and |form1|. EXPECT_TRUE( diff --git a/chromium/components/password_manager/core/browser/multi_store_password_save_manager.cc b/chromium/components/password_manager/core/browser/multi_store_password_save_manager.cc index a65c60f1efc..3aeb6fd22b1 100644 --- a/chromium/components/password_manager/core/browser/multi_store_password_save_manager.cc +++ b/chromium/components/password_manager/core/browser/multi_store_password_save_manager.cc @@ -4,12 +4,14 @@ #include "components/password_manager/core/browser/multi_store_password_save_manager.h" +#include "base/metrics/histogram_functions.h" #include "components/autofill/core/common/gaia_id_hash.h" #include "components/password_manager/core/browser/form_fetcher.h" #include "components/password_manager/core/browser/form_saver.h" #include "components/password_manager/core/browser/form_saver_impl.h" #include "components/password_manager/core/browser/password_feature_manager_impl.h" #include "components/password_manager/core/browser/password_form_metrics_recorder.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_manager_util.h" using autofill::PasswordForm; @@ -92,6 +94,20 @@ PendingCredentialsState ResolvePendingCredentialsStates( return PendingCredentialsState::NONE; } +// Returns a PasswordForm that has all fields taken from |update| except +// date_created, date_synced, times_used and moving_blocked_for_list that are +// taken from |original_form|. +PasswordForm UpdateFormPreservingDifferentFieldsAcrossStores( + const PasswordForm& original_form, + const PasswordForm& update) { + PasswordForm result(update); + result.date_created = original_form.date_created; + result.date_synced = original_form.date_synced; + result.times_used = original_form.times_used; + result.moving_blocked_for_list = original_form.moving_blocked_for_list; + return result; +} + } // namespace MultiStorePasswordSaveManager::MultiStorePasswordSaveManager( @@ -150,11 +166,18 @@ void MultiStorePasswordSaveManager::SavePendingToStoreImpl( case PendingCredentialsState::EQUAL_TO_SAVED_MATCH: { // If the submitted credentials exists in both stores, // |pending_credentials_| might be from the account store (and thus not - // have a moving_blocked_for_list). We need to preserve any existing list, - // so explicitly copy it over from the profile store match. - PasswordForm form_to_update(pending_credentials_); - form_to_update.moving_blocked_for_list = - states.similar_saved_form_from_profile_store->moving_blocked_for_list; + // have a moving_blocked_for_list). We need to preserve any existing list. + // Same applies for other fields. Check the comment on + // UpdateFormPreservingDifferentFieldsAcrossStores(). + PasswordForm form_to_update = + UpdateFormPreservingDifferentFieldsAcrossStores( + *states.similar_saved_form_from_profile_store, + pending_credentials_); + // For other cases, |pending_credentials_.times_used| is updated in + // UpdateMetadataForUsage() invoked from UploadVotesAndMetrics(). + // UpdateFormPreservingDifferentFieldsAcrossStores() preserved the + // original times_used, and hence we should increment it here. + form_to_update.times_used++; form_saver_->Update(form_to_update, profile_matches, old_profile_password); } break; @@ -177,10 +200,18 @@ void MultiStorePasswordSaveManager::SavePendingToStoreImpl( case PendingCredentialsState::EQUAL_TO_SAVED_MATCH: { // If the submitted credentials exists in both stores, // .|pending_credentials_| might be from the profile store (and thus - // has a moving_blocked_for_list). We need to clear it before storing to - // the account store. - PasswordForm form_to_update(pending_credentials_); - form_to_update.moving_blocked_for_list.clear(); + // has a moving_blocked_for_list). We need to preserve any existing + // values. Same applies for other fields. Check the comment on + // UpdateFormPreservingDifferentFieldsAcrossStores(). + PasswordForm form_to_update = + UpdateFormPreservingDifferentFieldsAcrossStores( + *states.similar_saved_form_from_account_store, + pending_credentials_); + // For other cases, |pending_credentials_.times_used| is updated in + // UpdateMetadataForUsage() invoked from UploadVotesAndMetrics(). + // UpdateFormPreservingDifferentFieldsAcrossStores() preserved the + // original times_used, and hence we should increment it here. + form_to_update.times_used++; account_store_form_saver_->Update(form_to_update, account_matches, old_account_password); } break; @@ -220,7 +251,11 @@ std::unique_ptr<PasswordSaveManager> MultiStorePasswordSaveManager::Clone() { return result; } -void MultiStorePasswordSaveManager::MoveCredentialsToAccountStore() { +void MultiStorePasswordSaveManager::MoveCredentialsToAccountStore( + metrics_util::MoveToAccountStoreTrigger trigger) { + base::UmaHistogramEnumeration( + "PasswordManager.AccountStorage.MoveToAccountStoreFlowAccepted", trigger); + // TODO(crbug.com/1032992): Moving credentials upon an update. FormFetch will // have an outdated credentials. Fix it if this turns out to be a product // requirement. diff --git a/chromium/components/password_manager/core/browser/multi_store_password_save_manager.h b/chromium/components/password_manager/core/browser/multi_store_password_save_manager.h index 095081df059..d90047f8ed5 100644 --- a/chromium/components/password_manager/core/browser/multi_store_password_save_manager.h +++ b/chromium/components/password_manager/core/browser/multi_store_password_save_manager.h @@ -33,7 +33,8 @@ class MultiStorePasswordSaveManager : public PasswordSaveManagerImpl { std::unique_ptr<PasswordSaveManager> Clone() override; - void MoveCredentialsToAccountStore() override; + void MoveCredentialsToAccountStore( + metrics_util::MoveToAccountStoreTrigger trigger) override; void BlockMovingToAccountStoreFor( const autofill::GaiaIdHash& gaia_id_hash) override; diff --git a/chromium/components/password_manager/core/browser/multi_store_password_save_manager_unittest.cc b/chromium/components/password_manager/core/browser/multi_store_password_save_manager_unittest.cc index 99278caec5e..9a9bc080c33 100644 --- a/chromium/components/password_manager/core/browser/multi_store_password_save_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/multi_store_password_save_manager_unittest.cc @@ -6,9 +6,11 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/metrics/histogram_tester.h" #include "components/autofill/core/common/renderer_id.h" #include "components/password_manager/core/browser/fake_form_fetcher.h" #include "components/password_manager/core/browser/password_form_metrics_recorder.h" +#include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/stub_form_saver.h" #include "components/password_manager/core/browser/stub_password_manager_client.h" #include "components/password_manager/core/browser/votes_uploader.h" @@ -35,6 +37,9 @@ MATCHER_P2(MatchesUsernameAndPassword, username, password, "") { const int kUsernameFieldIndex = 1; const int kPasswordFieldIndex = 2; +const auto kTrigger = metrics_util::MoveToAccountStoreTrigger:: + kSuccessfulLoginWithProfileStorePassword; + } // namespace class MockFormSaver : public StubFormSaver { @@ -69,17 +74,6 @@ class MockFormSaver : public StubFormSaver { DISALLOW_COPY_AND_ASSIGN(MockFormSaver); }; -class MockPasswordManagerClient : public StubPasswordManagerClient { - public: - MockPasswordManagerClient() = default; - ~MockPasswordManagerClient() override = default; - - MOCK_CONST_METHOD0(IsMainFrameSecure, bool()); - - private: - DISALLOW_COPY_AND_ASSIGN(MockPasswordManagerClient); -}; - class MultiStorePasswordSaveManagerTest : public testing::Test { public: MultiStorePasswordSaveManagerTest() @@ -122,7 +116,7 @@ class MultiStorePasswordSaveManagerTest : public testing::Test { submitted_form_.fields[kUsernameFieldIndex].value = ASCIIToUTF16("user1"); submitted_form_.fields[kPasswordFieldIndex].value = ASCIIToUTF16("secret1"); - saved_match_.origin = origin; + saved_match_.url = origin; saved_match_.action = action; saved_match_.signon_realm = "https://accounts.google.com/"; saved_match_.username_value = ASCIIToUTF16("test@gmail.com"); @@ -133,7 +127,7 @@ class MultiStorePasswordSaveManagerTest : public testing::Test { saved_match_.scheme = PasswordForm::Scheme::kHtml; psl_saved_match_ = saved_match_; - psl_saved_match_.origin = psl_origin; + psl_saved_match_.url = psl_origin; psl_saved_match_.action = psl_action; psl_saved_match_.signon_realm = "https://myaccounts.google.com/"; psl_saved_match_.is_public_suffix_match = true; @@ -156,7 +150,8 @@ class MultiStorePasswordSaveManagerTest : public testing::Test { fetcher_->Fetch(); metrics_recorder_ = base::MakeRefCounted<PasswordFormMetricsRecorder>( - client_.IsMainFrameSecure(), client_.GetUkmSourceId()); + client_.IsCommittedMainFrameSecure(), client_.GetUkmSourceId(), + /*pref_service=*/nullptr); auto mock_profile_form_saver = std::make_unique<NiceMock<MockFormSaver>>(); mock_profile_form_saver_ = mock_profile_form_saver.get(); @@ -195,7 +190,7 @@ class MultiStorePasswordSaveManagerTest : public testing::Test { PasswordForm CreateSavedFederated() { autofill::PasswordForm federated; - federated.origin = GURL("https://example.in/login"); + federated.url = GURL("https://example.in/login"); federated.signon_realm = "federation://example.in/google.com"; federated.type = autofill::PasswordForm::Type::kApi; federated.federation_origin = @@ -204,7 +199,7 @@ class MultiStorePasswordSaveManagerTest : public testing::Test { return federated; } - MockPasswordManagerClient* client() { return &client_; } + StubPasswordManagerClient* client() { return &client_; } MockFormSaver* mock_account_form_saver() { return mock_account_form_saver_; } MockFormSaver* mock_profile_form_saver() { return mock_profile_form_saver_; } FakeFormFetcher* fetcher() { return fetcher_.get(); } @@ -220,7 +215,7 @@ class MultiStorePasswordSaveManagerTest : public testing::Test { PasswordForm parsed_submitted_form_; private: - NiceMock<MockPasswordManagerClient> client_; + StubPasswordManagerClient client_; VotesUploader votes_uploader_; scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder_; @@ -411,15 +406,26 @@ TEST_F(MultiStorePasswordSaveManagerTest, UpdateInBothStores) { TEST_F(MultiStorePasswordSaveManagerTest, AutomaticSaveInBothStores) { SetAccountStoreEnabled(/*is_enabled=*/true); + // Set different values for the fields that should be preserved per store + // (namely: date_created, date_synced, times_used, moving_blocked_for_list) PasswordForm saved_match_in_profile_store(saved_match_); saved_match_in_profile_store.username_value = parsed_submitted_form_.username_value; saved_match_in_profile_store.password_value = parsed_submitted_form_.password_value; saved_match_in_profile_store.in_store = PasswordForm::Store::kProfileStore; + saved_match_in_profile_store.date_created = + base::Time::Now() - base::TimeDelta::FromDays(10); + saved_match_in_profile_store.times_used = 10; + saved_match_in_profile_store.moving_blocked_for_list.push_back( + autofill::GaiaIdHash::FromGaiaId("email@gmail.com")); PasswordForm saved_match_in_account_store(saved_match_in_profile_store); saved_match_in_account_store.in_store = PasswordForm::Store::kAccountStore; + saved_match_in_account_store.date_created = base::Time::Now(); + saved_match_in_account_store.date_synced = base::Time::Now(); + saved_match_in_account_store.times_used = 5; + saved_match_in_account_store.moving_blocked_for_list.clear(); SetNonFederatedAndNotifyFetchCompleted( {&saved_match_in_profile_store, &saved_match_in_account_store}); @@ -435,6 +441,7 @@ TEST_F(MultiStorePasswordSaveManagerTest, AutomaticSaveInBothStores) { // We still should update both credentials to update the |date_last_used| and // |times_used|. Note that |in_store| is irrelevant since it's not persisted. + // All other fields should be preserved. PasswordForm expected_profile_update_form(saved_match_in_profile_store); expected_profile_update_form.times_used++; expected_profile_update_form.date_last_used = @@ -754,6 +761,36 @@ TEST_F(MultiStorePasswordSaveManagerTest, } TEST_F(MultiStorePasswordSaveManagerTest, + MoveCredentialsFromProfileToAccountStoreRecordsFlowAccepted) { + base::HistogramTester histogram_tester; + + PasswordForm saved_match_in_profile_store(saved_match_); + saved_match_in_profile_store.in_store = PasswordForm::Store::kProfileStore; + saved_match_in_profile_store.moving_blocked_for_list.push_back( + autofill::GaiaIdHash::FromGaiaId("user@gmail.com")); + SetNonFederatedAndNotifyFetchCompleted({&saved_match_in_profile_store}); + + password_save_manager()->CreatePendingCredentials( + saved_match_in_profile_store, observed_form_, submitted_form_, + /*is_http_auth=*/false, + /*is_credential_api_save=*/false); + + PasswordForm saved_match_without_moving_blocked_list( + saved_match_in_profile_store); + saved_match_without_moving_blocked_list.moving_blocked_for_list.clear(); + + EXPECT_CALL(*mock_profile_form_saver(), Remove(saved_match_in_profile_store)); + EXPECT_CALL(*mock_account_form_saver(), + Save(saved_match_without_moving_blocked_list, _, _)); + + password_save_manager()->MoveCredentialsToAccountStore(kTrigger); + + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStorage.MoveToAccountStoreFlowAccepted", kTrigger, + 1); +} + +TEST_F(MultiStorePasswordSaveManagerTest, MoveCredentialsFromProfileToAccountStoreWhenExistsOnlyInProfileStore) { PasswordForm saved_match_in_profile_store(saved_match_); saved_match_in_profile_store.in_store = PasswordForm::Store::kProfileStore; @@ -774,7 +811,7 @@ TEST_F(MultiStorePasswordSaveManagerTest, EXPECT_CALL(*mock_account_form_saver(), Save(saved_match_without_moving_blocked_list, _, _)); - password_save_manager()->MoveCredentialsToAccountStore(); + password_save_manager()->MoveCredentialsToAccountStore(kTrigger); } TEST_F( @@ -797,7 +834,7 @@ TEST_F( Save(saved_match_in_profile_store, _, _)) .Times(0); - password_save_manager()->MoveCredentialsToAccountStore(); + password_save_manager()->MoveCredentialsToAccountStore(kTrigger); } TEST_F(MultiStorePasswordSaveManagerTest, @@ -823,7 +860,7 @@ TEST_F(MultiStorePasswordSaveManagerTest, EXPECT_CALL(*mock_account_form_saver(), Save(psl_saved_match_in_profile_store, _, _)); - password_save_manager()->MoveCredentialsToAccountStore(); + password_save_manager()->MoveCredentialsToAccountStore(kTrigger); } TEST_F(MultiStorePasswordSaveManagerTest, @@ -845,7 +882,7 @@ TEST_F(MultiStorePasswordSaveManagerTest, EXPECT_CALL(*mock_account_form_saver(), Save(federated_match_in_profile_store, _, _)); - password_save_manager()->MoveCredentialsToAccountStore(); + password_save_manager()->MoveCredentialsToAccountStore(kTrigger); } TEST_F(MultiStorePasswordSaveManagerTest, @@ -865,7 +902,7 @@ TEST_F(MultiStorePasswordSaveManagerTest, EXPECT_CALL(*mock_profile_form_saver(), Remove(saved_match_in_profile_store)); EXPECT_CALL(*mock_account_form_saver(), Save).Times(0); - password_save_manager()->MoveCredentialsToAccountStore(); + password_save_manager()->MoveCredentialsToAccountStore(kTrigger); } TEST_F(MultiStorePasswordSaveManagerTest, @@ -896,7 +933,7 @@ TEST_F(MultiStorePasswordSaveManagerTest, EXPECT_CALL(*mock_account_form_saver(), Save(saved_match_in_profile_store, _, _)); - password_save_manager()->MoveCredentialsToAccountStore(); + password_save_manager()->MoveCredentialsToAccountStore(kTrigger); } TEST_F(MultiStorePasswordSaveManagerTest, BlockMovingWhenExistsInProfileStore) { diff --git a/chromium/components/password_manager/core/browser/origin_credential_store.cc b/chromium/components/password_manager/core/browser/origin_credential_store.cc index 7e9f73026b7..12e75c3e583 100644 --- a/chromium/components/password_manager/core/browser/origin_credential_store.cc +++ b/chromium/components/password_manager/core/browser/origin_credential_store.cc @@ -35,9 +35,8 @@ UiCredential::UiCredential(const PasswordForm& form, const url::Origin& affiliated_origin) : username_(form.username_value), password_(form.password_value), - origin_(form.is_affiliation_based_match - ? affiliated_origin - : url::Origin::Create(form.origin)), + origin_(form.is_affiliation_based_match ? affiliated_origin + : url::Origin::Create(form.url)), is_public_suffix_match_(form.is_public_suffix_match), is_affiliation_based_match_(form.is_affiliation_based_match) {} diff --git a/chromium/components/password_manager/core/browser/password_autofill_manager.cc b/chromium/components/password_manager/core/browser/password_autofill_manager.cc index 48fb58924f8..a4960edf387 100644 --- a/chromium/components/password_manager/core/browser/password_autofill_manager.cc +++ b/chromium/components/password_manager/core/browser/password_autofill_manager.cc @@ -8,6 +8,7 @@ #include <algorithm> #include <string> +#include <tuple> #include <utility> #include <vector> @@ -20,6 +21,7 @@ #include "base/strings/string16.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "base/util/ranges/algorithm.h" #include "build/build_config.h" #include "components/autofill/core/browser/autofill_client.h" #include "components/autofill/core/browser/autofill_driver.h" @@ -52,6 +54,8 @@ namespace password_manager { namespace { +using AutoselectFirstSuggestion = + autofill::AutofillClient::PopupOpenArgs::AutoselectFirstSuggestion; using IsLoading = autofill::Suggestion::IsLoading; constexpr base::char16 kPasswordReplacementChar = 0x2022; @@ -224,8 +228,8 @@ autofill::Suggestion CreateEntryToOptInToAccountStorageThenFill() { // Entry for opting in to password account storage and then generating password. autofill::Suggestion CreateEntryToOptInToAccountStorageThenGenerate() { - autofill::Suggestion suggestion(l10n_util::GetStringUTF16( - IDS_PASSWORD_MANAGER_OPT_INTO_ACCOUNT_STORED_GENERATION)); + autofill::Suggestion suggestion( + l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_GENERATE_PASSWORD)); suggestion.frontend_id = autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE; suggestion.icon = "google"; @@ -302,6 +306,19 @@ std::vector<autofill::Suggestion> SetUnlockLoadingState( return new_suggestions; } +void LogAccountStoredPasswordsCountInFillDataAfterUnlock( + const autofill::PasswordFormFillData& fill_data) { + int account_store_passwords_count = + util::ranges::count_if(fill_data.additional_logins, + [](const autofill::PasswordAndMetadata& metadata) { + return metadata.uses_account_store; + }); + if (fill_data.uses_account_store) + ++account_store_passwords_count; + metrics_util::LogPasswordsCountFromAccountStoreAfterUnlock( + account_store_passwords_count); +} + } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -345,6 +362,7 @@ void PasswordAutofillManager::DidSelectSuggestion(const base::string16& value, void PasswordAutofillManager::OnUnlockItemAccepted( autofill::PopupItemId unlock_item) { + using metrics_util::PasswordDropdownSelectedOption; DCHECK( unlock_item == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN || unlock_item == @@ -352,10 +370,15 @@ void PasswordAutofillManager::OnUnlockItemAccepted( UpdatePopup(SetUnlockLoadingState(autofill_client_->GetPopupSuggestions(), unlock_item, IsLoading(true))); - autofill_client_->PinPopupView(); + signin_metrics::ReauthAccessPoint reauth_access_point = + unlock_item == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN + ? signin_metrics::ReauthAccessPoint::kAutofillDropdown + : signin_metrics::ReauthAccessPoint::kGeneratePasswordDropdown; password_client_->TriggerReauthForPrimaryAccount( + reauth_access_point, base::BindOnce(&PasswordAutofillManager::OnUnlockReauthCompleted, - weak_ptr_factory_.GetWeakPtr(), unlock_item)); + weak_ptr_factory_.GetWeakPtr(), unlock_item, + autofill_client_->GetReopenPopupArgs())); } void PasswordAutofillManager::DidAcceptSuggestion(const base::string16& value, @@ -389,18 +412,25 @@ void PasswordAutofillManager::DidAcceptSuggestion(const base::string16& value, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN) { password_client_->TriggerSignIn( signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN); + metrics_util::LogPasswordDropdownItemSelected( + PasswordDropdownSelectedOption::kResigninToUnlockAccountStore, + password_client_->IsIncognito()); } else if ( identifier == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN || identifier == autofill:: POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE) { OnUnlockItemAccepted(static_cast<autofill::PopupItemId>(identifier)); - return; // Do not hide the popup while loading data. + metrics_util::LogPasswordDropdownItemSelected( + identifier == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN + ? PasswordDropdownSelectedOption::kUnlockAccountStorePasswords + : PasswordDropdownSelectedOption::kUnlockAccountStoreGeneration, + password_client_->IsIncognito()); } else { + bool success = FillSuggestion(GetUsernameFromSuggestion(value), identifier); metrics_util::LogPasswordDropdownItemSelected( PasswordDropdownSelectedOption::kPassword, password_client_->IsIncognito()); - bool success = FillSuggestion(GetUsernameFromSuggestion(value), identifier); DCHECK(success); } @@ -453,10 +483,16 @@ void PasswordAutofillManager::OnAddPasswordFillData( return; fill_data_ = std::make_unique<autofill::PasswordFormFillData>(fill_data); - RequestFavicon(fill_data.origin); + RequestFavicon(fill_data.url); if (!autofill_client_ || autofill_client_->GetPopupSuggestions().empty()) return; + // Only log account-stored passwords if the unlock just happened. + if (HasLoadingSuggestion( + autofill_client_->GetPopupSuggestions(), + autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN)) { + LogAccountStoredPasswordsCountInFillDataAfterUnlock(fill_data); + } UpdatePopup(BuildSuggestions(base::string16(), ForPasswordField(AreSuggestionForPasswordField( autofill_client_->GetPopupSuggestions())), @@ -470,6 +506,8 @@ void PasswordAutofillManager::OnNoCredentialsFound() { autofill_client_->GetPopupSuggestions(), autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN)) return; + metrics_util::LogPasswordsCountFromAccountStoreAfterUnlock( + /*account_store_passwords_count=*/0); UpdatePopup({CreateAccountStorageEmptyEntry()}); } @@ -618,9 +656,10 @@ bool PasswordAutofillManager::ShowPopup( return false; } LogMetricsForSuggestions(suggestions); - autofill_client_->ShowAutofillPopup(bounds, text_direction, suggestions, - /*autoselect_first_suggestion=*/false, - autofill::PopupType::kPasswords, + autofill::AutofillClient::PopupOpenArgs open_args( + bounds, text_direction, suggestions, AutoselectFirstSuggestion(false), + autofill::PopupType::kPasswords); + autofill_client_->ShowAutofillPopup(open_args, weak_ptr_factory_.GetWeakPtr()); return true; } @@ -725,7 +764,11 @@ void PasswordAutofillManager::OnFaviconReady( void PasswordAutofillManager::OnUnlockReauthCompleted( autofill::PopupItemId unlock_item, + autofill::AutofillClient::PopupOpenArgs reopen_args, PasswordManagerClient::ReauthSucceeded reauth_succeeded) { + autofill_client_->ShowAutofillPopup(reopen_args, + weak_ptr_factory_.GetWeakPtr()); + autofill_client_->PinPopupView(); if (reauth_succeeded) { if (unlock_item == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE) { @@ -735,8 +778,8 @@ void PasswordAutofillManager::OnUnlockReauthCompleted( } return; } - UpdatePopup(SetUnlockLoadingState(autofill_client_->GetPopupSuggestions(), - unlock_item, IsLoading(false))); + UpdatePopup(SetUnlockLoadingState(reopen_args.suggestions, unlock_item, + IsLoading(false))); } } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_autofill_manager.h b/chromium/components/password_manager/core/browser/password_autofill_manager.h index e0dc5e9517e..d52c7091afa 100644 --- a/chromium/components/password_manager/core/browser/password_autofill_manager.h +++ b/chromium/components/password_manager/core/browser/password_autofill_manager.h @@ -169,8 +169,8 @@ class PasswordAutofillManager : public autofill::AutofillPopupDelegate { void OnFaviconReady(const favicon_base::FaviconImageResult& result); // Replaces |unlock_item| with a loading symbol and triggers a reauth flow to - // opt in for passwords account storage, with OnUnlockReauthCompleted as - // callback. + // opt in for the account-scoped password storage, with + // OnUnlockReauthCompleted as callback. void OnUnlockItemAccepted(autofill::PopupItemId unlock_item); // If reauth failed, resets the suggestions to show the |unlock_item| again. @@ -178,6 +178,7 @@ class PasswordAutofillManager : public autofill::AutofillPopupDelegate { // that was clicked. void OnUnlockReauthCompleted( autofill::PopupItemId unlock_item, + autofill::AutofillClient::PopupOpenArgs reopen_args, PasswordManagerClient::ReauthSucceeded reauth_succeeded); std::unique_ptr<autofill::PasswordFormFillData> fill_data_; diff --git a/chromium/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/chromium/components/password_manager/core/browser/password_autofill_manager_unittest.cc index eb355514ce0..e23bdd2e9c2 100644 --- a/chromium/components/password_manager/core/browser/password_autofill_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_autofill_manager_unittest.cc @@ -94,24 +94,29 @@ constexpr char kDropdownSelectedHistogram[] = "PasswordManager.PasswordDropdownItemSelected"; constexpr char kDropdownShownHistogram[] = "PasswordManager.PasswordDropdownShown"; +constexpr char kCredentialsCountFromAccountStoreAfterUnlockHistogram[] = + "PasswordManager.CredentialsCountFromAccountStoreAfterUnlock"; const gfx::Image kTestFavicon = gfx::test::CreateImage(16, 16); class MockPasswordManagerDriver : public StubPasswordManagerDriver { public: - MOCK_METHOD2(FillSuggestion, - void(const base::string16&, const base::string16&)); - MOCK_METHOD2(PreviewSuggestion, - void(const base::string16&, const base::string16&)); - MOCK_METHOD0(GetPasswordManager, PasswordManager*()); + MOCK_METHOD(void, + FillSuggestion, + (const base::string16&, const base::string16&), + (override)); + MOCK_METHOD(void, + PreviewSuggestion, + (const base::string16&, const base::string16&), + (override)); + MOCK_METHOD(PasswordManager*, GetPasswordManager, (), (override)); }; class TestPasswordManagerClient : public StubPasswordManagerClient { public: TestPasswordManagerClient() : main_frame_url_(kMainFrameUrl) {} - ~TestPasswordManagerClient() override = default; MockPasswordManagerDriver* mock_driver() { return &driver_; } - const GURL& GetMainFrameURL() const override { return main_frame_url_; } + const GURL& GetLastCommittedURL() const override { return main_frame_url_; } const MockPasswordFeatureManager* GetPasswordFeatureManager() const override { return feature_manager_.get(); @@ -139,12 +144,18 @@ class TestPasswordManagerClient : public StubPasswordManagerClient { .WillByDefault(Return(needs_signin)); } - MOCK_METHOD0(GeneratePassword, void()); - MOCK_METHOD1(TriggerReauthForPrimaryAccount, - void(base::OnceCallback<void(ReauthSucceeded)>)); - MOCK_METHOD1(TriggerSignIn, void(signin_metrics::AccessPoint)); - MOCK_METHOD0(GetFaviconService, favicon::FaviconService*()); - MOCK_METHOD1(NavigateToManagePasswordsPage, void(ManagePasswordsReferrer)); + MOCK_METHOD(void, GeneratePassword, (), (override)); + MOCK_METHOD(void, + TriggerReauthForPrimaryAccount, + (signin_metrics::ReauthAccessPoint, + base::OnceCallback<void(ReauthSucceeded)>), + (override)); + MOCK_METHOD(void, TriggerSignIn, (signin_metrics::AccessPoint), (override)); + MOCK_METHOD(favicon::FaviconService*, GetFaviconService, (), (override)); + MOCK_METHOD(void, + NavigateToManagePasswordsPage, + (ManagePasswordsReferrer), + (override)); private: MockPasswordManagerDriver driver_; @@ -157,20 +168,26 @@ class TestPasswordManagerClient : public StubPasswordManagerClient { class MockAutofillClient : public autofill::TestAutofillClient { public: MockAutofillClient() = default; - MOCK_METHOD6(ShowAutofillPopup, - void(const gfx::RectF& element_bounds, - base::i18n::TextDirection text_direction, - const std::vector<Suggestion>& suggestions, - bool autoselect_first_suggestion, - PopupType popup_type, - base::WeakPtr<autofill::AutofillPopupDelegate> delegate)); - MOCK_METHOD0(PinPopupView, void()); - MOCK_CONST_METHOD0(GetPopupSuggestions, - base::span<const autofill::Suggestion>()); - MOCK_METHOD2(UpdatePopup, - void(const std::vector<autofill::Suggestion>&, PopupType)); - MOCK_METHOD1(HideAutofillPopup, void(autofill::PopupHidingReason)); - MOCK_METHOD1(ExecuteCommand, void(int)); + MOCK_METHOD(void, + ShowAutofillPopup, + (const autofill::AutofillClient::PopupOpenArgs& open_args, + base::WeakPtr<autofill::AutofillPopupDelegate> delegate), + (override)); + MOCK_METHOD(void, PinPopupView, (), (override)); + MOCK_METHOD(PopupOpenArgs, GetReopenPopupArgs, (), (const, override)); + MOCK_METHOD(base::span<const autofill::Suggestion>, + GetPopupSuggestions, + (), + (const, override)); + MOCK_METHOD(void, + UpdatePopup, + (const std::vector<autofill::Suggestion>&, PopupType), + (override)); + MOCK_METHOD(void, + HideAutofillPopup, + (autofill::PopupHidingReason), + (override)); + MOCK_METHOD(void, ExecuteCommand, (int), (override)); }; base::CancelableTaskTracker::TaskId @@ -186,33 +203,53 @@ std::vector<autofill::Suggestion> CreateTestSuggestions( bool has_opt_in_and_generate, bool has_re_signin) { std::vector<Suggestion> suggestions; - suggestions.push_back( - Suggestion(/*value=*/"User1", /*label=*/"PW1", /*icon=*/"", - /*fronend_id=*/autofill::POPUP_ITEM_ID_PASSWORD_ENTRY)); - suggestions.push_back(Suggestion( + suggestions.emplace_back( + /*value=*/"User1", /*label=*/"PW1", /*icon=*/"", + /*frontend_id=*/autofill::POPUP_ITEM_ID_PASSWORD_ENTRY); + suggestions.emplace_back( /*value=*/"Show all pwds", /*label=*/"", /*icon=*/"", - /*fronend_id=*/autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY)); + /*frontend_id=*/autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY); if (has_opt_in_and_fill) { - suggestions.push_back(Suggestion( + suggestions.emplace_back( /*value=*/"Unlock passwords and fill", /*label=*/"", /*icon=*/"", - /*fronend_id=*/ - autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN)); + /*frontend_id=*/ + autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN); } if (has_opt_in_and_generate) { - suggestions.push_back(Suggestion( + suggestions.emplace_back( /*value=*/"Unlock passwords and generate", /*label=*/"", /*icon=*/"", - /*fronend_id=*/ - autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE)); + /*frontend_id=*/ + autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE); } if (has_re_signin) { - suggestions.push_back(Suggestion( + suggestions.emplace_back( /*value=*/"Sign in to access passwords", /*label=*/"", /*icon=*/"", - /*fronend_id=*/ - autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN)); + /*frontend_id=*/ + autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN); } return suggestions; } +std::vector<autofill::Suggestion> SetLoading( + std::vector<autofill::Suggestion> suggestions, + int index_of_loading_element) { + suggestions[index_of_loading_element].is_loading = + Suggestion::IsLoading(true); + return suggestions; +} + +autofill::AutofillClient::PopupOpenArgs CreateReopenArgsWithTestSuggestions( + bool has_opt_in_and_fill, + bool has_opt_in_and_generate, + bool has_re_signin) { + return { + gfx::RectF(), base::i18n::LEFT_TO_RIGHT, + CreateTestSuggestions(has_opt_in_and_fill, has_opt_in_and_generate, + has_re_signin), + autofill::AutofillClient::PopupOpenArgs::AutoselectFirstSuggestion(false), + autofill::PopupType::kPasswords}; +} + } // namespace class PasswordAutofillManagerTest : public testing::Test { @@ -242,7 +279,7 @@ class PasswordAutofillManagerTest : public testing::Test { EXPECT_CALL(*client, GetFaviconService()) .WillOnce(Return(&favicon_service)); EXPECT_CALL(favicon_service, - GetFaviconImageForPageURL(fill_data_.origin, _, _)); + GetFaviconImageForPageURL(fill_data_.url, _, _)); password_autofill_manager_->OnAddPasswordFillData(fill_data_); testing::Mock::VerifyAndClearExpectations(client); // Suppress the warnings in the tests. @@ -261,6 +298,17 @@ class PasswordAutofillManagerTest : public testing::Test { return l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_MANAGE_PASSWORDS); } + std::string GetManagePasswordsIcon() { + // The "Manage passwords" entry only has an icon if + // kEnablePasswordsAccountStorage is enabled. + std::string settings_icon; + if (base::FeatureList::IsEnabled( + password_manager::features::kEnablePasswordsAccountStorage)) { + return "settingsIcon"; + } + return std::string(); + } + protected: autofill::PasswordFormFillData& fill_data() { return fill_data_; } @@ -330,31 +378,29 @@ TEST_F(PasswordAutofillManagerTest, ExternalDelegatePasswordSuggestions) { data.uses_account_store = false; favicon::MockFaviconService favicon_service; EXPECT_CALL(client, GetFaviconService()).WillOnce(Return(&favicon_service)); - EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.origin, _, _)) + EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.url, _, _)) .WillOnce(Invoke(RespondWithTestIcon)); password_autofill_manager_->OnAddPasswordFillData(data); // Show the popup and verify the suggestions. - std::vector<Suggestion> suggestions; - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - _, _, - SuggestionVectorIdsAre( - ElementsAre(is_suggestion_on_password_field - ? autofill::POPUP_ITEM_ID_PASSWORD_ENTRY - : autofill::POPUP_ITEM_ID_USERNAME_ENTRY, - autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY)), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)) - .WillOnce(testing::SaveArg<2>(&suggestions)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); int show_suggestion_options = is_suggestion_on_password_field ? autofill::IS_PASSWORD_FIELD : 0; password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), show_suggestion_options, gfx::RectF()); - ASSERT_GE(suggestions.size(), 1u); - EXPECT_TRUE(AreImagesEqual(suggestions[0].custom_icon, kTestFavicon)); + ASSERT_GE(open_args.suggestions.size(), 1u); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIdsAre(ElementsAre( + is_suggestion_on_password_field + ? autofill::POPUP_ITEM_ID_PASSWORD_ENTRY + : autofill::POPUP_ITEM_ID_USERNAME_ENTRY, + autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY))); + EXPECT_TRUE( + AreImagesEqual(open_args.suggestions[0].custom_icon, kTestFavicon)); EXPECT_CALL(*client.mock_driver(), FillSuggestion(test_username_, test_password_)); @@ -396,33 +442,33 @@ TEST_F(PasswordAutofillManagerTest, data.additional_logins.push_back(duplicate); favicon::MockFaviconService favicon_service; EXPECT_CALL(client, GetFaviconService()).WillOnce(Return(&favicon_service)); - EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.origin, _, _)) + EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.url, _, _)) .WillOnce(Invoke(RespondWithTestIcon)); password_autofill_manager_->OnAddPasswordFillData(data); // Show the popup and verify local and account-stored suggestion coexist. - std::vector<Suggestion> suggestions; - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - _, _, - SuggestionVectorIdsAre(ElementsAre( - is_suggestion_on_password_field - ? autofill::POPUP_ITEM_ID_PASSWORD_ENTRY - : autofill::POPUP_ITEM_ID_USERNAME_ENTRY, - is_suggestion_on_password_field - ? autofill::POPUP_ITEM_ID_ACCOUNT_STORAGE_PASSWORD_ENTRY - : autofill::POPUP_ITEM_ID_ACCOUNT_STORAGE_USERNAME_ENTRY, - autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY)), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)) - .WillOnce(testing::SaveArg<2>(&suggestions)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), is_suggestion_on_password_field ? autofill::IS_PASSWORD_FIELD : 0, gfx::RectF()); - ASSERT_GE(suggestions.size(), 2u); - EXPECT_TRUE(AreImagesEqual(suggestions[0].custom_icon, kTestFavicon)); - EXPECT_TRUE(AreImagesEqual(suggestions[1].custom_icon, kTestFavicon)); + ASSERT_GE(open_args.suggestions.size(), 2u); + EXPECT_THAT( + open_args.suggestions, + SuggestionVectorIdsAre(ElementsAre( + is_suggestion_on_password_field + ? autofill::POPUP_ITEM_ID_PASSWORD_ENTRY + : autofill::POPUP_ITEM_ID_USERNAME_ENTRY, + is_suggestion_on_password_field + ? autofill::POPUP_ITEM_ID_ACCOUNT_STORAGE_PASSWORD_ENTRY + : autofill::POPUP_ITEM_ID_ACCOUNT_STORAGE_USERNAME_ENTRY, + autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY))); + EXPECT_TRUE( + AreImagesEqual(open_args.suggestions[0].custom_icon, kTestFavicon)); + EXPECT_TRUE( + AreImagesEqual(open_args.suggestions[1].custom_icon, kTestFavicon)); // When selecting the account-stored credential, make sure the filled // password belongs to the selected credential (and not to the first match). @@ -448,18 +494,19 @@ TEST_F(PasswordAutofillManagerTest, ShowOptInAndFillButton) { client.SetAccountStorageOptIn(false); // Show the popup and verify the suggestions. - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - _, _, - SuggestionVectorIdsAre(ElementsAre( - autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, - autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY, - autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN)), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), autofill::SHOW_ALL | autofill::IS_PASSWORD_FIELD, gfx::RectF()); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIdsAre(ElementsAre( + autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, + autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY, + autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } // Test that a popup without entries doesn't show "Manage all Passwords". @@ -471,16 +518,17 @@ TEST_F(PasswordAutofillManagerTest, SuppressManageAllWithoutPasswords) { client.SetAccountStorageOptIn(false); // Show the popup and verify the suggestions. - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - _, _, - SuggestionVectorIdsAre(ElementsAre( - autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN)), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), autofill::SHOW_ALL | autofill::IS_PASSWORD_FIELD, gfx::RectF()); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIdsAre(ElementsAre( + autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } // Test that the popup is updated once account-stored suggestions are unlocked. @@ -491,18 +539,19 @@ TEST_F(PasswordAutofillManagerTest, ShowResigninButton) { client.SetNeedsReSigninForAccountStorage(true); // Show the popup and verify the suggestions. - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - _, _, - SuggestionVectorIdsAre(ElementsAre( - autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, - autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY, - autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN)), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), autofill::SHOW_ALL | autofill::IS_PASSWORD_FIELD, gfx::RectF()); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIdsAre(ElementsAre( + autofill::POPUP_ITEM_ID_PASSWORD_ENTRY, + autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY, + autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } // Test that the popup is updated once "opt in and fill" is clicked. @@ -525,7 +574,6 @@ TEST_F(PasswordAutofillManagerTest, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN)), PopupType::kPasswords)) .WillOnce(testing::SaveArg<0>(&suggestions)); - EXPECT_CALL(autofill_client, PinPopupView); EXPECT_CALL(client, TriggerReauthForPrimaryAccount); EXPECT_CALL(autofill_client, GetPopupSuggestions()) .WillOnce(Return(CreateTestSuggestions(/*has_opt_in_and_fill=*/true, @@ -560,7 +608,6 @@ TEST_F(PasswordAutofillManagerTest, POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE)), PopupType::kPasswords)) .WillOnce(testing::SaveArg<0>(&suggestions)); - EXPECT_CALL(autofill_client, PinPopupView); EXPECT_CALL(client, TriggerReauthForPrimaryAccount); EXPECT_CALL(autofill_client, GetPopupSuggestions()) .WillOnce(Return(CreateTestSuggestions(/*has_opt_in_and_fill=*/false, @@ -606,19 +653,21 @@ TEST_F(PasswordAutofillManagerTest, FailedOptInAndFillUpdatesPopup) { .WillOnce(Return(CreateTestSuggestions(/*has_opt_in_and_fill=*/true, /*has_opt_in_and_generate*/ false, /*has_re_signin=*/false))); - EXPECT_CALL(autofill_client, UpdatePopup); - // As soon as the waiting state is pending, the next update resets the popup. - EXPECT_CALL(autofill_client, PinPopupView).WillOnce([&] { + EXPECT_CALL(autofill_client, UpdatePopup).WillOnce([&] { testing::Mock::VerifyAndClear(&autofill_client); - EXPECT_CALL(autofill_client, GetPopupSuggestions) - .WillOnce(Return(CreateTestSuggestions( + EXPECT_CALL(autofill_client, GetReopenPopupArgs) + .WillOnce(Return(CreateReopenArgsWithTestSuggestions( /*has_opt_in_and_fill=*/true, /*has_opt_in_and_generate*/ false, /*has_re_signin=*/false))); - EXPECT_CALL(client, TriggerReauthForPrimaryAccount) - .WillOnce([](auto reauth_callback) { + EXPECT_CALL(client, + TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint::kAutofillDropdown, _)) + .WillOnce([](auto, auto reauth_callback) { std::move(reauth_callback).Run(ReauthSucceeded(false)); }); + EXPECT_CALL(autofill_client, ShowAutofillPopup); + EXPECT_CALL(autofill_client, PinPopupView); EXPECT_CALL( autofill_client, UpdatePopup( @@ -653,19 +702,22 @@ TEST_F(PasswordAutofillManagerTest, FailedOptInAndGenerateUpdatesPopup) { .WillOnce(Return(CreateTestSuggestions(/*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ true, /*has_re_signin=*/false))); - EXPECT_CALL(autofill_client, UpdatePopup); - // As soon as the waiting state is pending, the next update resets the popup. - EXPECT_CALL(autofill_client, PinPopupView).WillOnce([&] { + EXPECT_CALL(autofill_client, UpdatePopup).WillOnce([&] { testing::Mock::VerifyAndClear(&autofill_client); - EXPECT_CALL(autofill_client, GetPopupSuggestions) - .WillOnce(Return(CreateTestSuggestions(/*has_opt_in_and_fill=*/false, - /*has_opt_in_and_generate*/ true, - /*has_re_signin=*/false))); - EXPECT_CALL(client, TriggerReauthForPrimaryAccount) - .WillOnce([](auto reauth_callback) { + EXPECT_CALL(autofill_client, GetReopenPopupArgs) + .WillOnce(Return(CreateReopenArgsWithTestSuggestions( + /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ true, + /*has_re_signin=*/false))); + EXPECT_CALL( + client, + TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint::kGeneratePasswordDropdown, _)) + .WillOnce([](auto, auto reauth_callback) { std::move(reauth_callback).Run(ReauthSucceeded(false)); }); + EXPECT_CALL(autofill_client, ShowAutofillPopup); + EXPECT_CALL(autofill_client, PinPopupView); EXPECT_CALL( autofill_client, UpdatePopup( @@ -700,12 +752,18 @@ TEST_F(PasswordAutofillManagerTest, SuccessfullOptInAndFillHidesPopup) { /*has_opt_in_and_generate*/ false, /*has_re_signin=*/false))); EXPECT_CALL(autofill_client, UpdatePopup); - EXPECT_CALL(autofill_client, PinPopupView); - - EXPECT_CALL(client, TriggerReauthForPrimaryAccount) - .WillOnce([](auto reauth_callback) { + EXPECT_CALL(autofill_client, GetReopenPopupArgs) + .WillOnce(Return(CreateReopenArgsWithTestSuggestions( + /*has_opt_in_and_fill=*/true, /*has_opt_in_and_generate*/ false, + /*has_re_signin=*/false))); + EXPECT_CALL(client, + TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint::kAutofillDropdown, _)) + .WillOnce([](auto, auto reauth_callback) { std::move(reauth_callback).Run(ReauthSucceeded(true)); }); + EXPECT_CALL(autofill_client, ShowAutofillPopup); + EXPECT_CALL(autofill_client, PinPopupView); password_autofill_manager_->DidAcceptSuggestion( test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN, @@ -728,15 +786,22 @@ TEST_F(PasswordAutofillManagerTest, /*has_opt_in_and_generate*/ true, /*has_re_signin=*/false))); EXPECT_CALL(autofill_client, UpdatePopup); - EXPECT_CALL(autofill_client, PinPopupView); - - EXPECT_CALL(client, TriggerReauthForPrimaryAccount) - .WillOnce([](auto reauth_callback) { + EXPECT_CALL(autofill_client, GetReopenPopupArgs) + .WillOnce(Return(CreateReopenArgsWithTestSuggestions( + /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ true, + /*has_re_signin=*/false))); + EXPECT_CALL( + client, + TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint::kGeneratePasswordDropdown, _)) + .WillOnce([](auto, auto reauth_callback) { std::move(reauth_callback).Run(ReauthSucceeded(true)); }); - EXPECT_CALL( - autofill_client, - HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion)); + EXPECT_CALL(autofill_client, ShowAutofillPopup); + EXPECT_CALL(autofill_client, PinPopupView); + EXPECT_CALL(autofill_client, + HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion)) + .Times(testing::AtLeast(1)); EXPECT_CALL(client, GeneratePassword()); password_autofill_manager_->DidAcceptSuggestion( @@ -748,6 +813,7 @@ TEST_F(PasswordAutofillManagerTest, TEST_F(PasswordAutofillManagerTest, SuccessfullOptInMayShowEmptyState) { TestPasswordManagerClient client; NiceMock<MockAutofillClient> autofill_client; + base::HistogramTester histograms; InitializePasswordAutofillManager(&client, &autofill_client); client.SetAccountStorageOptIn(true); testing::Mock::VerifyAndClearExpectations(&autofill_client); @@ -771,6 +837,8 @@ TEST_F(PasswordAutofillManagerTest, SuccessfullOptInMayShowEmptyState) { password_autofill_manager_->DeleteFillData(); password_autofill_manager_->OnNoCredentialsFound(); + histograms.ExpectBucketCount( + kCredentialsCountFromAccountStoreAfterUnlockHistogram, 0, 1); } // Test that the popup is updated once "opt in and fill" is clicked". @@ -778,6 +846,7 @@ TEST_F(PasswordAutofillManagerTest, AddOnFillDataAfterOptInAndFillPopulatesPopup) { TestPasswordManagerClient client; NiceMock<MockAutofillClient> autofill_client; + base::HistogramTester histograms; InitializePasswordAutofillManager(&client, &autofill_client); client.SetAccountStorageOptIn(true); testing::Mock::VerifyAndClearExpectations(&autofill_client); @@ -790,9 +859,11 @@ TEST_F(PasswordAutofillManagerTest, additional.username = base::ASCIIToUTF16("bar.foo@example.com"); new_data.additional_logins.push_back(std::move(additional)); EXPECT_CALL(autofill_client, GetPopupSuggestions()) - .WillRepeatedly(Return(CreateTestSuggestions( - /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ false, - /*has_re_signin=*/false))); + .WillRepeatedly(Return(SetLoading( + CreateTestSuggestions( + /*has_opt_in_and_fill=*/true, /*has_opt_in_and_generate*/ false, + /*has_re_signin=*/false), + /*index_of_loading_element=*/2))); // Opt-in is at third position. EXPECT_CALL(autofill_client, HideAutofillPopup(autofill::PopupHidingReason::kStaleData)); EXPECT_CALL( @@ -805,6 +876,8 @@ TEST_F(PasswordAutofillManagerTest, password_autofill_manager_->DeleteFillData(); password_autofill_manager_->OnAddPasswordFillData(new_data); + histograms.ExpectBucketCount( + kCredentialsCountFromAccountStoreAfterUnlockHistogram, 1, 1); } // Test that OnShowPasswordSuggestions correctly matches the given FormFieldData @@ -827,44 +900,40 @@ TEST_F(PasswordAutofillManagerTest, ExtractSuggestions) { // First, simulate displaying suggestions matching an empty prefix. Also // verify that both the values and labels are filled correctly. The 'value' // should be the user name; the 'label' should be the realm. - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - element_bounds, _, - testing::AllOf( - SuggestionVectorValuesAre(testing::UnorderedElementsAre( - test_username_, additional.username, - GetManagePasswordsTitle())), - SuggestionVectorLabelsAre(testing::AllOf( - testing::Contains(base::UTF8ToUTF16("foo.com")), - testing::Contains(base::UTF8ToUTF16("foobarrealm.org"))))), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), 0, element_bounds); + EXPECT_THAT( + open_args.suggestions, + SuggestionVectorValuesAre(testing::UnorderedElementsAre( + test_username_, additional.username, GetManagePasswordsTitle()))); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorLabelsAre( + testing::Contains(base::UTF8ToUTF16("foo.com")))); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorLabelsAre( + testing::Contains(base::UTF8ToUTF16("foobarrealm.org")))); // Now simulate displaying suggestions matching "John". - EXPECT_CALL( - autofill_client, - ShowAutofillPopup(element_bounds, _, - SuggestionVectorValuesAre(ElementsAre( - additional.username, GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)); - + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::ASCIIToUTF16("John"), 0, element_bounds); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre( + ElementsAre(additional.username, GetManagePasswordsTitle()))); // Finally, simulate displaying all suggestions, without any prefix matching. - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - element_bounds, _, - SuggestionVectorValuesAre(ElementsAre( - test_username_, additional.username, GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::ASCIIToUTF16("xyz"), autofill::SHOW_ALL, element_bounds); + EXPECT_THAT(open_args.suggestions, SuggestionVectorValuesAre(ElementsAre( + test_username_, additional.username, + GetManagePasswordsTitle()))); } // Verify that, for Android application credentials, the prettified realms of @@ -886,17 +955,19 @@ TEST_F(PasswordAutofillManagerTest, PrettifiedAndroidRealmsAreShownAsLabels) { password_autofill_manager_->OnAddPasswordFillData(data); - EXPECT_CALL(autofill_client, - ShowAutofillPopup(_, _, - SuggestionVectorLabelsAre(testing::AllOf( - testing::Contains(base::ASCIIToUTF16( - "android://com.example1.android/")), - testing::Contains(base::ASCIIToUTF16( - "android://com.example2.android/")))), - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), 0, gfx::RectF()); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorLabelsAre(testing::Contains( + base::ASCIIToUTF16("android://com.example2.android/")))); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorLabelsAre(testing::Contains( + base::ASCIIToUTF16("android://com.example1.android/")))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } TEST_F(PasswordAutofillManagerTest, FillSuggestionPasswordField) { @@ -914,15 +985,17 @@ TEST_F(PasswordAutofillManagerTest, FillSuggestionPasswordField) { password_autofill_manager_->OnAddPasswordFillData(data); - EXPECT_CALL(autofill_client, - ShowAutofillPopup(element_bounds, _, - SuggestionVectorValuesAre(ElementsAre( - test_username_, GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, test_username_, autofill::IS_PASSWORD_FIELD, element_bounds); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre( + ElementsAre(test_username_, GetManagePasswordsTitle()))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } // Verify that typing "foo" into the username field will match usernames @@ -950,15 +1023,16 @@ TEST_F(PasswordAutofillManagerTest, DisplaySuggestionsWithMatchingTokens) { password_autofill_manager_->OnAddPasswordFillData(data); - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - element_bounds, _, - SuggestionVectorValuesAre(testing::UnorderedElementsAre( - username, additional.username, GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::ASCIIToUTF16("foo"), 0, element_bounds); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre(testing::UnorderedElementsAre( + username, additional.username, GetManagePasswordsTitle()))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } // Verify that typing "oo" into the username field will not match any usernames @@ -1018,16 +1092,17 @@ TEST_F(PasswordAutofillManagerTest, password_autofill_manager_->OnAddPasswordFillData(data); - EXPECT_CALL( - autofill_client, - ShowAutofillPopup(element_bounds, _, - SuggestionVectorValuesAre(ElementsAre( - additional.username, GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::ASCIIToUTF16("foo@exam"), 0, element_bounds); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre( + ElementsAre(additional.username, GetManagePasswordsTitle()))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } // Verify that typing "example" into the username field will match and order @@ -1057,16 +1132,17 @@ TEST_F(PasswordAutofillManagerTest, password_autofill_manager_->OnAddPasswordFillData(data); - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - element_bounds, _, - SuggestionVectorValuesAre(ElementsAre(username, additional.username, - GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::ASCIIToUTF16("foo"), false, element_bounds); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre(ElementsAre( + username, additional.username, GetManagePasswordsTitle()))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } TEST_F(PasswordAutofillManagerTest, PreviewAndFillEmptyUsernameSuggestion) { @@ -1126,12 +1202,9 @@ TEST_F(PasswordAutofillManagerTest, ShowAllPasswordsOptionOnPasswordField) { password_autofill_manager_->OnAddPasswordFillData(data); - EXPECT_CALL(autofill_client, - ShowAutofillPopup(element_bounds, _, - SuggestionVectorValuesAre(ElementsAre( - test_username_, GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, test_username_, autofill::IS_PASSWORD_FIELD, @@ -1139,6 +1212,9 @@ TEST_F(PasswordAutofillManagerTest, ShowAllPasswordsOptionOnPasswordField) { histograms.ExpectUniqueSample(kDropdownShownHistogram, metrics_util::PasswordDropdownState::kStandard, 1); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre( + ElementsAre(test_username_, GetManagePasswordsTitle()))); // Expect a sample only in the shown histogram. histograms.ExpectUniqueSample( @@ -1193,14 +1269,16 @@ TEST_F(PasswordAutofillManagerTest, ShowAllPasswordsOptionOnNonPasswordField) { password_autofill_manager_->OnAddPasswordFillData(data); - EXPECT_CALL(autofill_client, - ShowAutofillPopup(element_bounds, _, - SuggestionVectorValuesAre(ElementsAre( - test_username_, GetManagePasswordsTitle())), - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, test_username_, 0, element_bounds); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre( + ElementsAre(test_username_, GetManagePasswordsTitle()))); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } TEST_F(PasswordAutofillManagerTest, @@ -1230,22 +1308,15 @@ TEST_F(PasswordAutofillManagerTest, favicon::MockFaviconService favicon_service; EXPECT_CALL(client, GetFaviconService()).WillOnce(Return(&favicon_service)); - EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.origin, _, _)); + EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.url, _, _)); password_autofill_manager_->OnAddPasswordFillData(data); - // Bring up the drop-down with the generaion option. + // Bring up the drop-down with the generation option. base::string16 generation_string = l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_GENERATE_PASSWORD); - EXPECT_CALL( - autofill_client, - ShowAutofillPopup(element_bounds, base::i18n::RIGHT_TO_LEFT, - AllOf(SuggestionVectorValuesAre( - ElementsAre(test_username_, generation_string, - GetManagePasswordsTitle())), - SuggestionVectorIconsAre(ElementsAre( - "globeIcon", "keyIcon", std::string()))), - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); EXPECT_TRUE( password_autofill_manager_->MaybeShowPasswordSuggestionsWithGeneration( element_bounds, base::i18n::RIGHT_TO_LEFT, @@ -1253,6 +1324,12 @@ TEST_F(PasswordAutofillManagerTest, histograms.ExpectUniqueSample( kDropdownShownHistogram, metrics_util::PasswordDropdownState::kStandardGenerate, 1); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorIconsAre(ElementsAre("globeIcon", "keyIcon", + GetManagePasswordsIcon()))); + EXPECT_THAT(open_args.suggestions, SuggestionVectorValuesAre(ElementsAre( + test_username_, generation_string, + GetManagePasswordsTitle()))); // Click "Generate password". EXPECT_CALL(client, GeneratePassword()); @@ -1277,26 +1354,25 @@ TEST_F(PasswordAutofillManagerTest, favicon::MockFaviconService favicon_service; EXPECT_CALL(client, GetFaviconService()).WillOnce(Return(&favicon_service)); - EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.origin, _, _)); + EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.url, _, _)); password_autofill_manager_->OnAddPasswordFillData(data); base::string16 generation_string = l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_GENERATE_PASSWORD); - EXPECT_CALL( - autofill_client, - ShowAutofillPopup( - element_bounds, base::i18n::RIGHT_TO_LEFT, - AllOf( - SuggestionVectorValuesAre( - ElementsAre(generation_string, GetManagePasswordsTitle())), - SuggestionVectorIconsAre(ElementsAre("keyIcon", std::string()))), - /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); EXPECT_TRUE( password_autofill_manager_->MaybeShowPasswordSuggestionsWithGeneration( element_bounds, base::i18n::RIGHT_TO_LEFT, /*show_password_suggestions=*/false)); + EXPECT_THAT(open_args.suggestions, SuggestionVectorIconsAre(ElementsAre( + "keyIcon", GetManagePasswordsIcon()))); + EXPECT_THAT(open_args.suggestions, + SuggestionVectorValuesAre( + ElementsAre(generation_string, GetManagePasswordsTitle()))); } // Test that if the "opt in and generate" button gets displayed, the regular @@ -1316,25 +1392,26 @@ TEST_F(PasswordAutofillManagerTest, favicon::MockFaviconService favicon_service; EXPECT_CALL(client, GetFaviconService()).WillOnce(Return(&favicon_service)); - EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.origin, _, _)); + EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.url, _, _)); password_autofill_manager_->OnAddPasswordFillData(data); auto opt_in_and_generate_id = static_cast<int>( autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE); auto regular_generate_id = static_cast<int>(autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY); - EXPECT_CALL(autofill_client, - ShowAutofillPopup( - _, _, - AllOf(Contains(Field(&autofill::Suggestion::frontend_id, - Eq(opt_in_and_generate_id))), - Not(Contains(Field(&autofill::Suggestion::frontend_id, - Eq(regular_generate_id))))), - _, _, _)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->MaybeShowPasswordSuggestionsWithGeneration( gfx::RectF(), base::i18n::RIGHT_TO_LEFT, /*show_password_suggestions=*/true); + EXPECT_THAT(open_args.suggestions, + Not(Contains(Field(&autofill::Suggestion::frontend_id, + Eq(regular_generate_id))))); + EXPECT_THAT(open_args.suggestions, + Contains(Field(&autofill::Suggestion::frontend_id, + Eq(opt_in_and_generate_id)))); } TEST_F(PasswordAutofillManagerTest, DisplayAccountSuggestionsIndicatorIcon) { @@ -1353,16 +1430,16 @@ TEST_F(PasswordAutofillManagerTest, DisplayAccountSuggestionsIndicatorIcon) { password_autofill_manager_->OnAddPasswordFillData(data); - std::vector<autofill::Suggestion> suggestions; - EXPECT_CALL(autofill_client, - ShowAutofillPopup(element_bounds, _, _, - /*autoselect_first_suggestion=*/false, - PopupType::kPasswords, _)) - .WillOnce(testing::SaveArg<2>(&suggestions)); + autofill::AutofillClient::PopupOpenArgs open_args; + EXPECT_CALL(autofill_client, ShowAutofillPopup) + .WillOnce(testing::SaveArg<0>(&open_args)); password_autofill_manager_->OnShowPasswordSuggestions( base::i18n::RIGHT_TO_LEFT, base::string16(), false, element_bounds); - ASSERT_THAT(suggestions.size(), testing::Ge(1u)); // No footer on Android. - EXPECT_THAT(suggestions[0].store_indicator_icon, "google"); + ASSERT_THAT(open_args.suggestions.size(), + testing::Ge(1u)); // No footer on Android. + EXPECT_THAT(open_args.suggestions[0].store_indicator_icon, "google"); + EXPECT_FALSE(open_args.autoselect_first_suggestion); + EXPECT_EQ(open_args.popup_type, PopupType::kPasswords); } } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_bubble_experiment_unittest.cc b/chromium/components/password_manager/core/browser/password_bubble_experiment_unittest.cc index d2643e08e9b..044be044686 100644 --- a/chromium/components/password_manager/core/browser/password_bubble_experiment_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_bubble_experiment_unittest.cc @@ -6,6 +6,7 @@ #include <ostream> +#include "base/feature_list.h" #include "base/strings/string_number_conversions.h" #include "base/test/scoped_feature_list.h" #include "components/password_manager/core/common/password_manager_features.h" @@ -108,6 +109,13 @@ TEST_F(PasswordManagerPasswordBubbleExperimentTest, #if !defined(OS_CHROMEOS) TEST_F(PasswordManagerPasswordBubbleExperimentTest, ReviveSignInPasswordPromo) { + // If kEnablePasswordsAccountStorage is enabled, then the password manager + // bubble never shows Sync promos, so this test doesn't apply. + if (base::FeatureList::IsEnabled( + password_manager::features::kEnablePasswordsAccountStorage)) { + return; + } + sync_service()->SetDisableReasons(syncer::SyncService::DisableReasonSet()); sync_service()->SetFirstSetupComplete(false); sync_service()->SetTransportState( diff --git a/chromium/components/password_manager/core/browser/password_feature_manager.h b/chromium/components/password_manager/core/browser/password_feature_manager.h index 00c9775763c..b6dce401651 100644 --- a/chromium/components/password_manager/core/browser/password_feature_manager.h +++ b/chromium/components/password_manager/core/browser/password_feature_manager.h @@ -44,11 +44,12 @@ class PasswordFeatureManager { // associated settings (e.g. default store choice). virtual void OptOutOfAccountStorageAndClearSettings() = 0; - // Whether it makes sense to ask the user about the store when saving a - // password (i.e. profile or account store). This is true if the user has - // opted in already, or hasn't opted in but all other requirements are met - // (i.e. there is a signed-in user, Sync-the-feature is not enabled, etc). - virtual bool ShouldShowPasswordStorePicker() const = 0; + // Whether it makes sense to ask the user to move a password or about the + // store when saving a password (i.e. profile or account store). This is true + // if the user has opted in already, or hasn't opted in but all other + // requirements are met (i.e. there is a signed-in user, Sync-the-feature is + // not enabled, etc). + virtual bool ShouldShowAccountStorageBubbleUi() const = 0; // Sets the default password store selected by user in prefs. This store is // used for saving new credentials and adding blacking listing entries. diff --git a/chromium/components/password_manager/core/browser/password_feature_manager_impl.cc b/chromium/components/password_manager/core/browser/password_feature_manager_impl.cc index b04e8b1479f..ed983ea7a67 100644 --- a/chromium/components/password_manager/core/browser/password_feature_manager_impl.cc +++ b/chromium/components/password_manager/core/browser/password_feature_manager_impl.cc @@ -59,9 +59,9 @@ void PasswordFeatureManagerImpl::SetDefaultPasswordStore( features_util::SetDefaultPasswordStore(pref_service_, sync_service_, store); } -bool PasswordFeatureManagerImpl::ShouldShowPasswordStorePicker() const { - return features_util::ShouldShowPasswordStorePicker(pref_service_, - sync_service_); +bool PasswordFeatureManagerImpl::ShouldShowAccountStorageBubbleUi() const { + return features_util::ShouldShowAccountStorageBubbleUi(pref_service_, + sync_service_); } PasswordForm::Store PasswordFeatureManagerImpl::GetDefaultPasswordStore() diff --git a/chromium/components/password_manager/core/browser/password_feature_manager_impl.h b/chromium/components/password_manager/core/browser/password_feature_manager_impl.h index 9793f1bfc86..8505e3c343c 100644 --- a/chromium/components/password_manager/core/browser/password_feature_manager_impl.h +++ b/chromium/components/password_manager/core/browser/password_feature_manager_impl.h @@ -32,7 +32,7 @@ class PasswordFeatureManagerImpl : public PasswordFeatureManager { void OptInToAccountStorage() override; void OptOutOfAccountStorageAndClearSettings() override; - bool ShouldShowPasswordStorePicker() const override; + bool ShouldShowAccountStorageBubbleUi() const override; void SetDefaultPasswordStore( const autofill::PasswordForm::Store& store) override; diff --git a/chromium/components/password_manager/core/browser/password_form_filling.cc b/chromium/components/password_manager/core/browser/password_form_filling.cc index b4c65f63292..b6158b072dc 100644 --- a/chromium/components/password_manager/core/browser/password_form_filling.cc +++ b/chromium/components/password_manager/core/browser/password_form_filling.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/browser/password_form_filling.h" +#include <memory> + #include "base/feature_list.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/user_metrics.h" @@ -13,6 +15,7 @@ #include "components/autofill/core/common/password_form_fill_data.h" #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h" #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" +#include "components/password_manager/core/browser/password_feature_manager.h" #include "components/password_manager/core/browser/password_form_metrics_recorder.h" #include "components/password_manager/core/browser/password_manager_client.h" #include "components/password_manager/core/browser/password_manager_driver.h" @@ -94,8 +97,8 @@ void Autofill(PasswordManagerClient* client, std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client->GetLogManager()); logger->LogMessage(Logger::STRING_PASSWORDMANAGER_AUTOFILL); } @@ -110,7 +113,8 @@ void Autofill(PasswordManagerClient* client, PreferredRealmIsFromAndroid(fill_data)); driver->FillPasswordForm(fill_data); - client->PasswordWasAutofilled(best_matches, form_for_autofill.origin, + client->PasswordWasAutofilled(best_matches, + url::Origin::Create(form_for_autofill.url), &federated_matches); } @@ -138,7 +142,10 @@ LikelyFormFilling SendFillInformationToRenderer( } if (best_matches.empty()) { - driver->InformNoSavedCredentials(); + bool should_show_popup_without_passwords = + client->GetPasswordFeatureManager()->ShouldShowAccountStorageOptIn() || + client->GetPasswordFeatureManager()->ShouldShowAccountStorageReSignin(); + driver->InformNoSavedCredentials(should_show_popup_without_passwords); metrics_recorder->RecordFillEvent( PasswordFormMetricsRecorder::kManagerFillEventNoCredential); return LikelyFormFilling::kNoFilling; @@ -169,7 +176,7 @@ LikelyFormFilling SendFillInformationToRenderer( } else if (no_sign_in_form) { // If the parser did not find a current password element, don't fill. wait_for_username_reason = WaitForUsernameReason::kFormNotGoodForFilling; - } else if (!client->IsMainFrameSecure()) { + } else if (!client->IsCommittedMainFrameSecure()) { wait_for_username_reason = WaitForUsernameReason::kInsecureOrigin; } else if (autofill::IsTouchToFillEnabled()) { wait_for_username_reason = WaitForUsernameReason::kTouchToFill; @@ -217,7 +224,7 @@ PasswordFormFillData CreatePasswordFormFillData( result.form_renderer_id = form_on_page.form_data.unique_renderer_id; result.name = form_on_page.form_data.name; - result.origin = form_on_page.origin; + result.url = form_on_page.url; result.action = form_on_page.action; result.uses_account_store = preferred_match.IsUsingAccountStore(); result.wait_for_username = wait_for_username; diff --git a/chromium/components/password_manager/core/browser/password_form_filling_unittest.cc b/chromium/components/password_manager/core/browser/password_form_filling_unittest.cc index a65babb7577..98935fe7b0d 100644 --- a/chromium/components/password_manager/core/browser/password_form_filling_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_form_filling_unittest.cc @@ -52,26 +52,23 @@ constexpr char kSyncedPassword[] = "password"; class MockPasswordManagerDriver : public StubPasswordManagerDriver { public: - MockPasswordManagerDriver() {} - - ~MockPasswordManagerDriver() override = default; - - MOCK_CONST_METHOD0(GetId, int()); - MOCK_METHOD1(FillPasswordForm, void(const PasswordFormFillData&)); - MOCK_METHOD0(InformNoSavedCredentials, void()); - MOCK_METHOD1(ShowInitialPasswordAccountSuggestions, - void(const PasswordFormFillData&)); - MOCK_METHOD1(AllowPasswordGenerationForForm, void(const PasswordForm&)); + MOCK_METHOD(int, GetId, (), (const, override)); + MOCK_METHOD(void, + FillPasswordForm, + (const PasswordFormFillData&), + (override)); + MOCK_METHOD(void, InformNoSavedCredentials, (bool), (override)); }; class MockPasswordManagerClient : public StubPasswordManagerClient { public: - MOCK_METHOD3(PasswordWasAutofilled, - void(const std::vector<const PasswordForm*>&, - const GURL&, - const std::vector<const PasswordForm*>*)); - - MOCK_CONST_METHOD0(IsMainFrameSecure, bool()); + MOCK_METHOD(void, + PasswordWasAutofilled, + (const std::vector<const PasswordForm*>&, + const url::Origin&, + const std::vector<const PasswordForm*>*), + (override)); + MOCK_METHOD(bool, IsCommittedMainFrameSecure, (), (const, override)); }; PasswordForm CreateForm(std::string username, @@ -105,9 +102,9 @@ PasswordFormFillData::LoginCollection::const_iterator FindPasswordByUsername( class PasswordFormFillingTest : public testing::Test { public: PasswordFormFillingTest() { - ON_CALL(client_, IsMainFrameSecure()).WillByDefault(Return(true)); + ON_CALL(client_, IsCommittedMainFrameSecure()).WillByDefault(Return(true)); - observed_form_.origin = GURL("https://accounts.google.com/a/LoginAuth"); + observed_form_.url = GURL("https://accounts.google.com/a/LoginAuth"); observed_form_.action = GURL("https://accounts.google.com/a/Login"); observed_form_.username_element = ASCIIToUTF16("Email"); observed_form_.username_element_renderer_id = @@ -120,21 +117,20 @@ class PasswordFormFillingTest : public testing::Test { observed_form_.form_data.name = ASCIIToUTF16("the-form-name"); saved_match_ = observed_form_; - saved_match_.origin = - GURL("https://accounts.google.com/a/ServiceLoginAuth"); + saved_match_.url = GURL("https://accounts.google.com/a/ServiceLoginAuth"); saved_match_.action = GURL("https://accounts.google.com/a/ServiceLogin"); saved_match_.username_value = ASCIIToUTF16("test@gmail.com"); saved_match_.password_value = ASCIIToUTF16("test1"); psl_saved_match_ = saved_match_; psl_saved_match_.is_public_suffix_match = true; - psl_saved_match_.origin = + psl_saved_match_.url = GURL("https://m.accounts.google.com/a/ServiceLoginAuth"); psl_saved_match_.action = GURL("https://m.accounts.google.com/a/Login"); psl_saved_match_.signon_realm = "https://m.accounts.google.com"; metrics_recorder_ = base::MakeRefCounted<PasswordFormMetricsRecorder>( - true, client_.GetUkmSourceId()); + true, client_.GetUkmSourceId(), /*pref_service=*/nullptr); } protected: @@ -150,9 +146,8 @@ class PasswordFormFillingTest : public testing::Test { TEST_F(PasswordFormFillingTest, NoSavedCredentials) { std::vector<const PasswordForm*> best_matches; - EXPECT_CALL(driver_, InformNoSavedCredentials()); + EXPECT_CALL(driver_, InformNoSavedCredentials(_)); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(0); - EXPECT_CALL(driver_, ShowInitialPasswordAccountSuggestions(_)).Times(0); LikelyFormFilling likely_form_filling = SendFillInformationToRenderer( &client_, &driver_, observed_form_, best_matches, federated_matches_, @@ -168,11 +163,10 @@ TEST_F(PasswordFormFillingTest, Autofill) { another_saved_match.password_value += ASCIIToUTF16("1"); best_matches.push_back(&another_saved_match); - EXPECT_CALL(driver_, InformNoSavedCredentials()).Times(0); + EXPECT_CALL(driver_, InformNoSavedCredentials(_)).Times(0); PasswordFormFillData fill_data; EXPECT_CALL(driver_, FillPasswordForm(_)).WillOnce(SaveArg<0>(&fill_data)); - EXPECT_CALL(driver_, ShowInitialPasswordAccountSuggestions(_)).Times(0); - EXPECT_CALL(client_, PasswordWasAutofilled(_, _, _)); + EXPECT_CALL(client_, PasswordWasAutofilled); LikelyFormFilling likely_form_filling = SendFillInformationToRenderer( &client_, &driver_, observed_form_, best_matches, federated_matches_, @@ -181,7 +175,7 @@ TEST_F(PasswordFormFillingTest, Autofill) { // Check that the message to the renderer (i.e. |fill_data|) is filled // correctly. - EXPECT_EQ(observed_form_.origin, fill_data.origin); + EXPECT_EQ(observed_form_.url, fill_data.url); EXPECT_FALSE(fill_data.wait_for_username); EXPECT_EQ(observed_form_.username_element, fill_data.username_field.name); EXPECT_EQ(saved_match_.username_value, fill_data.username_field.value); @@ -237,7 +231,7 @@ TEST_F(PasswordFormFillingTest, TestFillOnLoadSuggestion) { PasswordFormFillData fill_data; EXPECT_CALL(driver_, FillPasswordForm(_)).WillOnce(SaveArg<0>(&fill_data)); - EXPECT_CALL(client_, PasswordWasAutofilled(_, _, _)); + EXPECT_CALL(client_, PasswordWasAutofilled); LikelyFormFilling likely_form_filling = SendFillInformationToRenderer( &client_, &driver_, observed_form, best_matches, federated_matches_, @@ -256,11 +250,10 @@ TEST_F(PasswordFormFillingTest, TestFillOnLoadSuggestion) { TEST_F(PasswordFormFillingTest, AutofillPSLMatch) { std::vector<const PasswordForm*> best_matches = {&psl_saved_match_}; - EXPECT_CALL(driver_, InformNoSavedCredentials()).Times(0); + EXPECT_CALL(driver_, InformNoSavedCredentials(_)).Times(0); PasswordFormFillData fill_data; EXPECT_CALL(driver_, FillPasswordForm(_)).WillOnce(SaveArg<0>(&fill_data)); - EXPECT_CALL(driver_, ShowInitialPasswordAccountSuggestions(_)).Times(0); - EXPECT_CALL(client_, PasswordWasAutofilled(_, _, _)); + EXPECT_CALL(client_, PasswordWasAutofilled); LikelyFormFilling likely_form_filling = SendFillInformationToRenderer( &client_, &driver_, observed_form_, best_matches, federated_matches_, @@ -269,7 +262,7 @@ TEST_F(PasswordFormFillingTest, AutofillPSLMatch) { // Check that the message to the renderer (i.e. |fill_data|) is filled // correctly. - EXPECT_EQ(observed_form_.origin, fill_data.origin); + EXPECT_EQ(observed_form_.url, fill_data.url); EXPECT_TRUE(fill_data.wait_for_username); EXPECT_EQ(psl_saved_match_.signon_realm, fill_data.preferred_realm); EXPECT_EQ(observed_form_.username_element, fill_data.username_field.name); @@ -280,20 +273,19 @@ TEST_F(PasswordFormFillingTest, AutofillPSLMatch) { TEST_F(PasswordFormFillingTest, NoAutofillOnHttp) { PasswordForm observed_http_form = observed_form_; - observed_http_form.origin = GURL("http://accounts.google.com/a/LoginAuth"); + observed_http_form.url = GURL("http://accounts.google.com/a/LoginAuth"); observed_http_form.action = GURL("http://accounts.google.com/a/Login"); observed_http_form.signon_realm = "http://accounts.google.com"; PasswordForm saved_http_match = saved_match_; - saved_http_match.origin = - GURL("http://accounts.google.com/a/ServiceLoginAuth"); + saved_http_match.url = GURL("http://accounts.google.com/a/ServiceLoginAuth"); saved_http_match.action = GURL("http://accounts.google.com/a/ServiceLogin"); saved_http_match.signon_realm = "http://accounts.google.com"; ASSERT_FALSE(GURL(saved_http_match.signon_realm).SchemeIsCryptographic()); std::vector<const PasswordForm*> best_matches = {&saved_http_match}; - EXPECT_CALL(client_, IsMainFrameSecure).WillOnce(Return(false)); + EXPECT_CALL(client_, IsCommittedMainFrameSecure).WillOnce(Return(false)); LikelyFormFilling likely_form_filling = SendFillInformationToRenderer( &client_, &driver_, observed_http_form, best_matches, federated_matches_, &saved_http_match, metrics_recorder_.get()); @@ -326,7 +318,7 @@ TEST_F(PasswordFormFillingTest, TouchToFill) { TEST(PasswordFormFillDataTest, TestSinglePreferredMatch) { // Create the current form on the page. PasswordForm form_on_page; - form_on_page.origin = GURL("https://foo.com/"); + form_on_page.url = GURL("https://foo.com/"); form_on_page.action = GURL("https://foo.com/login"); form_on_page.username_element = ASCIIToUTF16("username"); form_on_page.username_value = ASCIIToUTF16(kPreferredUsername); @@ -338,7 +330,7 @@ TEST(PasswordFormFillDataTest, TestSinglePreferredMatch) { // Create an exact match in the database. PasswordForm preferred_match; - preferred_match.origin = GURL("https://foo.com/"); + preferred_match.url = GURL("https://foo.com/"); preferred_match.action = GURL("https://foo.com/login"); preferred_match.username_element = ASCIIToUTF16("username"); preferred_match.username_value = ASCIIToUTF16(kPreferredUsername); @@ -375,7 +367,7 @@ TEST(PasswordFormFillDataTest, TestSinglePreferredMatch) { TEST(PasswordFormFillDataTest, TestPublicSuffixDomainMatching) { // Create the current form on the page. PasswordForm form_on_page; - form_on_page.origin = GURL("https://foo.com/"); + form_on_page.url = GURL("https://foo.com/"); form_on_page.action = GURL("https://foo.com/login"); form_on_page.username_element = ASCIIToUTF16("username"); form_on_page.username_value = ASCIIToUTF16(kPreferredUsername); @@ -387,7 +379,7 @@ TEST(PasswordFormFillDataTest, TestPublicSuffixDomainMatching) { // Create a match from the database that matches using public suffix. PasswordForm preferred_match; - preferred_match.origin = GURL("https://mobile.foo.com/"); + preferred_match.url = GURL("https://mobile.foo.com/"); preferred_match.action = GURL("https://mobile.foo.com/login"); preferred_match.username_element = ASCIIToUTF16("username"); preferred_match.username_value = ASCIIToUTF16(kPreferredUsername); @@ -401,7 +393,7 @@ TEST(PasswordFormFillDataTest, TestPublicSuffixDomainMatching) { // Create a match that matches exactly, so |is_public_suffix_match| has a // default value false. PasswordForm exact_match; - exact_match.origin = GURL("https://foo.com/"); + exact_match.url = GURL("https://foo.com/"); exact_match.action = GURL("https://foo.com/login"); exact_match.username_element = ASCIIToUTF16("username"); exact_match.username_value = ASCIIToUTF16("test1@gmail.com"); @@ -414,7 +406,7 @@ TEST(PasswordFormFillDataTest, TestPublicSuffixDomainMatching) { // Create a match that was matched using public suffix, so // |is_public_suffix_match| == true. PasswordForm public_suffix_match; - public_suffix_match.origin = GURL("https://foo.com/"); + public_suffix_match.url = GURL("https://foo.com/"); public_suffix_match.action = GURL("https://foo.com/login"); public_suffix_match.username_element = ASCIIToUTF16("username"); public_suffix_match.username_value = ASCIIToUTF16("test2@gmail.com"); @@ -456,7 +448,7 @@ TEST(PasswordFormFillDataTest, TestPublicSuffixDomainMatching) { TEST(PasswordFormFillDataTest, TestAffiliationMatch) { // Create the current form on the page. PasswordForm form_on_page; - form_on_page.origin = GURL("https://foo.com/"); + form_on_page.url = GURL("https://foo.com/"); form_on_page.action = GURL("https://foo.com/login"); form_on_page.username_element = ASCIIToUTF16("username"); form_on_page.username_value = ASCIIToUTF16(kPreferredUsername); @@ -468,7 +460,7 @@ TEST(PasswordFormFillDataTest, TestAffiliationMatch) { // Create a match from the database that matches using affiliation. PasswordForm preferred_match; - preferred_match.origin = GURL("android://hash@foo.com/"); + preferred_match.url = GURL("android://hash@foo.com/"); preferred_match.username_value = ASCIIToUTF16(kPreferredUsername); preferred_match.password_value = ASCIIToUTF16(kPreferredPassword); preferred_match.signon_realm = "android://hash@foo.com/"; @@ -477,7 +469,7 @@ TEST(PasswordFormFillDataTest, TestAffiliationMatch) { // Create a match that matches exactly, so |is_affiliation_based_match| has a // default value false. PasswordForm exact_match; - exact_match.origin = GURL("https://foo.com/"); + exact_match.url = GURL("https://foo.com/"); exact_match.action = GURL("https://foo.com/login"); exact_match.username_element = ASCIIToUTF16("username"); exact_match.username_value = ASCIIToUTF16("test1@gmail.com"); @@ -490,7 +482,7 @@ TEST(PasswordFormFillDataTest, TestAffiliationMatch) { // Create a match that was matched using public suffix, so // |is_public_suffix_match| == true. PasswordForm affiliated_match; - affiliated_match.origin = GURL("android://hash@foo1.com/"); + affiliated_match.url = GURL("android://hash@foo1.com/"); affiliated_match.username_value = ASCIIToUTF16("test2@gmail.com"); affiliated_match.password_value = ASCIIToUTF16(kPreferredPassword); affiliated_match.is_affiliation_based_match = true; @@ -524,7 +516,7 @@ TEST(PasswordFormFillDataTest, TestAffiliationMatch) { TEST(PasswordFormFillDataTest, RendererIDs) { // Create the current form on the page. PasswordForm form_on_page; - form_on_page.origin = GURL("https://foo.com/"); + form_on_page.url = GURL("https://foo.com/"); form_on_page.action = GURL("https://foo.com/login"); form_on_page.username_element = ASCIIToUTF16("username"); form_on_page.password_element = ASCIIToUTF16("password"); @@ -561,7 +553,7 @@ TEST(PasswordFormFillDataTest, RendererIDs) { TEST(PasswordFormFillDataTest, NoPasswordElement) { // Create the current form on the page. PasswordForm form_on_page; - form_on_page.origin = GURL("https://foo.com/"); + form_on_page.url = GURL("https://foo.com/"); form_on_page.has_renderer_ids = true; form_on_page.username_element_renderer_id = FieldRendererId(123); // Set no password element. diff --git a/chromium/components/password_manager/core/browser/password_form_manager.cc b/chromium/components/password_manager/core/browser/password_form_manager.cc index a60398c9070..05fba4ff31f 100644 --- a/chromium/components/password_manager/core/browser/password_form_manager.cc +++ b/chromium/components/password_manager/core/browser/password_form_manager.cc @@ -27,6 +27,7 @@ #include "components/password_manager/core/browser/password_generation_manager.h" #include "components/password_manager/core/browser/password_manager_client.h" #include "components/password_manager/core/browser/password_manager_driver.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/possible_username_data.h" #include "components/password_manager/core/browser/statistics_table.h" @@ -34,8 +35,10 @@ #include "components/signin/public/identity_manager/identity_manager.h" #include "google_apis/gaia/core_account_id.h" +using autofill::FieldRendererId; using autofill::FormData; using autofill::FormFieldData; +using autofill::FormRendererId; using autofill::FormSignature; using autofill::FormStructure; using autofill::GaiaIdHash; @@ -188,13 +191,7 @@ bool PasswordFormManager::DoesManageAccordingToRendererId( const PasswordManagerDriver* driver) const { if (driver != driver_.get()) return false; -#if defined(OS_IOS) - NOTREACHED(); - // On iOS form name is used as the form identifier. - return false; -#else return observed_form_.unique_renderer_id == form_renderer_id; -#endif } bool PasswordFormManager::IsEqualToSubmittedForm( @@ -229,8 +226,8 @@ bool PasswordFormManager::IsEqualToSubmittedForm( return false; } -const GURL& PasswordFormManager::GetOrigin() const { - return observed_not_web_form_digest_ ? observed_not_web_form_digest_->origin +const GURL& PasswordFormManager::GetURL() const { + return observed_not_web_form_digest_ ? observed_not_web_form_digest_->url : observed_form_.url; } @@ -271,18 +268,21 @@ bool PasswordFormManager::IsBlacklisted() const { return form_fetcher_->IsBlacklisted() || newly_blacklisted_; } +bool PasswordFormManager::WasUnblacklisted() const { + return was_unblacklisted_while_on_page_; +} + bool PasswordFormManager::IsMovableToAccountStore() const { + DCHECK( + client_->GetPasswordFeatureManager()->ShouldShowAccountStorageBubbleUi()) + << "Ensure that the client supports moving passwords for this user!"; signin::IdentityManager* identity_manager = client_->GetIdentityManager(); - if (!identity_manager) - return false; + DCHECK(identity_manager); const std::string gaia_id = identity_manager ->GetPrimaryAccountInfo(signin::ConsentLevel::kNotRequired) .gaia; - // If there is no signed in user, we cannot move the credentials to the - // account store. - if (gaia_id.empty()) - return false; + DCHECK(!gaia_id.empty()) << "Cannot move without signed in user"; const base::string16& username = GetPendingCredentials().username_value; const base::string16& password = GetPendingCredentials().password_value; @@ -418,18 +418,18 @@ void PasswordFormManager::PermanentlyBlacklist() { PasswordStore::FormDigest PasswordFormManager::ConstructObservedFormDigest() { std::string signon_realm; - GURL origin; + GURL url; if (observed_not_web_form_digest_) { - origin = observed_not_web_form_digest_->origin; + url = observed_not_web_form_digest_->url; // GetSignonRealm is not suitable for http auth credentials. signon_realm = IsHttpAuth() ? observed_not_web_form_digest_->signon_realm - : GetSignonRealm(observed_not_web_form_digest_->origin); + : GetSignonRealm(observed_not_web_form_digest_->url); } else { - origin = observed_form_.url; + url = observed_form_.url; signon_realm = GetSignonRealm(observed_form_.url); } - return PasswordStore::FormDigest(GetScheme(), signon_realm, origin); + return PasswordStore::FormDigest(GetScheme(), signon_realm, url); } void PasswordFormManager::OnPasswordsRevealed() { @@ -437,7 +437,10 @@ void PasswordFormManager::OnPasswordsRevealed() { } void PasswordFormManager::MoveCredentialsToAccountStore() { - password_save_manager_->MoveCredentialsToAccountStore(); + DCHECK(client_->GetPasswordFeatureManager()->IsOptedInForAccountStorage()); + password_save_manager_->MoveCredentialsToAccountStore( + metrics_util::MoveToAccountStoreTrigger:: + kSuccessfulLoginWithProfileStorePassword); } void PasswordFormManager::BlockMovingCredentialsToAccountStore() { @@ -497,7 +500,7 @@ void PasswordFormManager::SetGenerationPopupWasShown( } void PasswordFormManager::SetGenerationElement( - const base::string16& generation_element) { + FieldRendererId generation_element) { votes_uploader_.set_generation_element(generation_element); } @@ -523,22 +526,22 @@ void PasswordFormManager::PresaveGeneratedPassword( PasswordManagerDriver* driver, const FormData& form, const base::string16& generated_password, - const base::string16& generation_element) { + FieldRendererId generation_element) { observed_form_ = form; PresaveGeneratedPasswordInternal(form, generated_password); votes_uploader_.set_generation_element(generation_element); } bool PasswordFormManager::UpdateStateOnUserInput( - const base::string16& form_identifier, - const base::string16& field_identifier, + FormRendererId form_id, + FieldRendererId field_id, const base::string16& field_value) { - if (observed_form_.name != form_identifier) + if (observed_form_.unique_renderer_id != form_id) return false; bool form_data_changed = false; for (FormFieldData& field : observed_form_.fields) { - if (field.unique_id == field_identifier) { + if (field.unique_renderer_id == field_id) { field.value = field_value; form_data_changed = true; break; @@ -550,7 +553,7 @@ bool PasswordFormManager::UpdateStateOnUserInput( base::string16 generated_password = password_save_manager_->GetGeneratedPassword(); - if (votes_uploader_.get_generation_element() == field_identifier) { + if (votes_uploader_.get_generation_element() == field_id) { generated_password = field_value; form_data_changed = true; } @@ -596,8 +599,8 @@ std::unique_ptr<PasswordFormManager> PasswordFormManager::Clone() { result->parser_.set_predictions(*parser_.predictions()); if (parsed_submitted_form_) { - result->parsed_submitted_form_.reset( - new PasswordForm(*parsed_submitted_form_)); + result->parsed_submitted_form_ = + std::make_unique<PasswordForm>(*parsed_submitted_form_); } result->is_submitted_ = is_submitted_; result->password_save_manager_->Init(result->client_, result->form_fetcher_, @@ -637,7 +640,7 @@ void PasswordFormManager::OnFetchCompleted() { return; } - client_->UpdateCredentialCache(observed_form_.url.GetOrigin(), + client_->UpdateCredentialCache(url::Origin::Create(observed_form_.url), form_fetcher_->GetBestMatches(), form_fetcher_->IsBlacklisted()); @@ -723,7 +726,7 @@ bool PasswordFormManager::ProvisionallySaveHttpAuthForm( PasswordStore::FormDigest(submitted_form))) return false; - parsed_submitted_form_.reset(new PasswordForm(submitted_form)); + parsed_submitted_form_ = std::make_unique<PasswordForm>(submitted_form); is_submitted_ = true; CreatePendingCredentials(); return true; @@ -788,7 +791,6 @@ void PasswordFormManager::Fill() { driver_->FormEligibleForGenerationFound( {/*form_renderer_id*/ observed_password_form->form_data .unique_renderer_id, - /*new_password_element*/ observed_password_form->new_password_element, /*new_password_element_renderer_id*/ observed_password_form->new_password_element_renderer_id, /*confirmation_password_element_renderer_id*/ @@ -845,14 +847,18 @@ void PasswordFormManager::OnGeneratedPasswordAccepted( ParseFormAndMakeLogging(form_data, FormDataParser::Mode::kSaving); if (!parsed_form) { // Create a password form with a minimum data. - parsed_form.reset(new PasswordForm); - parsed_form->origin = form_data.url; + parsed_form = std::make_unique<PasswordForm>(); + parsed_form->url = form_data.url; parsed_form->signon_realm = GetSignonRealm(form_data.url); } parsed_form->password_value = password; password_save_manager_->GeneratedPasswordAccepted(*parsed_form, driver_); } +void PasswordFormManager::MarkWasUnblacklisted() { + was_unblacklisted_while_on_page_ = true; +} + PasswordFormManager::PasswordFormManager( PasswordManagerClient* client, FormFetcher* form_fetcher, @@ -874,7 +880,8 @@ PasswordFormManager::PasswordFormManager( votes_uploader_(client, false /* is_possible_change_password_form */) { if (!metrics_recorder_) { metrics_recorder_ = base::MakeRefCounted<PasswordFormMetricsRecorder>( - client_->IsMainFrameSecure(), client_->GetUkmSourceId()); + client_->IsCommittedMainFrameSecure(), client_->GetUkmSourceId(), + client_->GetPrefs()); } password_save_manager_->Init(client_, form_fetcher_, metrics_recorder_, &votes_uploader_); @@ -936,8 +943,8 @@ void PasswordFormManager::PresaveGeneratedPasswordInternal( if (!parsed_form) { // Create a password form with a minimum data. - parsed_form.reset(new PasswordForm()); - parsed_form->origin = form.url; + parsed_form = std::make_unique<PasswordForm>(); + parsed_form->url = form.url; parsed_form->signon_realm = GetSignonRealm(form.url); } // Set |password_value| to the generated password in order to ensure that diff --git a/chromium/components/password_manager/core/browser/password_form_manager.h b/chromium/components/password_manager/core/browser/password_form_manager.h index f045bafce00..74888661cca 100644 --- a/chromium/components/password_manager/core/browser/password_form_manager.h +++ b/chromium/components/password_manager/core/browser/password_form_manager.h @@ -134,8 +134,11 @@ class PasswordFormManager : public PasswordFormManagerForUI, autofill::FieldRendererId generation_element_id, const base::string16& password); + // Sets |was_unblacklisted_while_on_page| to true. + void MarkWasUnblacklisted(); + // PasswordFormManagerForUI: - const GURL& GetOrigin() const override; + const GURL& GetURL() const override; const std::vector<const autofill::PasswordForm*>& GetBestMatches() const override; std::vector<const autofill::PasswordForm*> GetFederatedMatches() @@ -147,6 +150,7 @@ class PasswordFormManager : public PasswordFormManagerForUI, base::span<const CompromisedCredentials> GetCompromisedCredentials() const override; bool IsBlacklisted() const override; + bool WasUnblacklisted() const override; bool IsMovableToAccountStore() const override; void Save() override; @@ -170,7 +174,7 @@ class PasswordFormManager : public PasswordFormManagerForUI, void PasswordNoLongerGenerated(); bool HasGeneratedPassword() const; void SetGenerationPopupWasShown(bool is_manual_generation); - void SetGenerationElement(const base::string16& generation_element); + void SetGenerationElement(autofill::FieldRendererId generation_element); bool IsPossibleChangePasswordFormWithoutUsername() const; bool IsPasswordUpdate() const; base::WeakPtr<PasswordManagerDriver> GetDriver() const; @@ -186,14 +190,14 @@ class PasswordFormManager : public PasswordFormManagerForUI, void PresaveGeneratedPassword(PasswordManagerDriver* driver, const autofill::FormData& form, const base::string16& generated_password, - const base::string16& generation_element); + autofill::FieldRendererId generation_element); // Return false and do nothing if |form_identifier| does not correspond to // |observed_form_|. Otherwise set a value of the field with // |field_identifier| of |observed_form_| to |field_value|. In case if there // is a presaved credential this function updates the presaved credential. - bool UpdateStateOnUserInput(const base::string16& form_identifier, - const base::string16& field_identifier, + bool UpdateStateOnUserInput(autofill::FormRendererId form_id, + autofill::FieldRendererId field_id, const base::string16& field_value); void SetDriver(const base::WeakPtr<PasswordManagerDriver>& driver); @@ -313,6 +317,11 @@ class PasswordFormManager : public PasswordFormManagerForUI, // this boolean to false again. bool newly_blacklisted_ = false; + // Set to true when the user unblacklists the origin while on the page. + // This is used to decide when to record + // |PasswordManager.ResultOfSavingAfterUnblacklisting|. + bool was_unblacklisted_while_on_page_ = false; + // Takes care of recording metrics and events for |*this|. scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder_; diff --git a/chromium/components/password_manager/core/browser/password_form_manager_for_ui.h b/chromium/components/password_manager/core/browser/password_form_manager_for_ui.h index d75801ab615..e070e3ee520 100644 --- a/chromium/components/password_manager/core/browser/password_form_manager_for_ui.h +++ b/chromium/components/password_manager/core/browser/password_form_manager_for_ui.h @@ -28,8 +28,8 @@ class PasswordFormManagerForUI { public: virtual ~PasswordFormManagerForUI() = default; - // Returns origin of the initially observed form. - virtual const GURL& GetOrigin() const = 0; + // Returns URL of the initially observed form. + virtual const GURL& GetURL() const = 0; // Returns the best saved matches for the observed form. virtual const std::vector<const autofill::PasswordForm*>& GetBestMatches() @@ -63,6 +63,9 @@ class PasswordFormManagerForUI { // Determines if the user opted to 'never remember' passwords for this form. virtual bool IsBlacklisted() const = 0; + // Checks if the user unblacklisted the origin of the form for saving. + virtual bool WasUnblacklisted() const = 0; + // Determines whether the submitted credentials returned by // GetPendingCredentials() can be moved to the signed in account store. // Returns true if the submitted credentials are stored in the profile store diff --git a/chromium/components/password_manager/core/browser/password_form_manager_unittest.cc b/chromium/components/password_manager/core/browser/password_form_manager_unittest.cc index 40e181ca6ef..09874961a3a 100644 --- a/chromium/components/password_manager/core/browser/password_form_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_form_manager_unittest.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/browser/password_form_manager.h" +#include <memory> + #include <string> #include <type_traits> #include <utility> @@ -29,6 +31,7 @@ #include "components/password_manager/core/browser/field_info_manager.h" #include "components/password_manager/core/browser/multi_store_password_save_manager.h" #include "components/password_manager/core/browser/password_form_manager_for_ui.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_save_manager_impl.h" #include "components/password_manager/core/browser/password_store.h" @@ -38,6 +41,11 @@ #include "components/password_manager/core/browser/stub_password_manager_driver.h" #include "components/password_manager/core/browser/vote_uploads_test_matchers.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_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/identity_manager/identity_test_environment.h" #include "components/signin/public/identity_manager/identity_test_utils.h" #include "components/ukm/test_ukm_recorder.h" @@ -46,6 +54,7 @@ using autofill::AutofillUploadContents; using autofill::FieldPropertiesFlags; +using autofill::FieldRendererId; using autofill::FormData; using autofill::FormFieldData; using autofill::FormRendererId; @@ -102,10 +111,6 @@ MATCHER_P(FormDataEqualTo, form_data, "") { class MockPasswordManagerDriver : public StubPasswordManagerDriver { public: - MockPasswordManagerDriver() {} - - ~MockPasswordManagerDriver() override {} - MOCK_METHOD1(FillPasswordForm, void(const PasswordFormFillData&)); MOCK_METHOD1(AllowPasswordGenerationForForm, void(const PasswordForm&)); MOCK_METHOD1(FormEligibleForGenerationFound, @@ -129,7 +134,7 @@ class MockAutofillDownloadManager : public autofill::AutofillDownloadManager { class StubObserver : public AutofillDownloadManager::Observer { void OnLoadedServerPredictions( std::string response, - const std::vector<std::string>& form_signatures) override {} + const autofill::FormAndFieldSignatures& form_signatures) override {} }; StubObserver fake_observer; @@ -138,30 +143,27 @@ class MockAutofillDownloadManager : public autofill::AutofillDownloadManager { class MockPasswordManagerClient : public StubPasswordManagerClient { public: - MockPasswordManagerClient() = default; - ~MockPasswordManagerClient() override = default; - - MOCK_CONST_METHOD0(IsIncognito, bool()); - - MOCK_METHOD0(GetAutofillDownloadManager, - autofill::AutofillDownloadManager*()); - - MOCK_METHOD0(UpdateFormManagers, void()); - - MOCK_METHOD2(AutofillHttpAuth, - void(const PasswordForm&, const PasswordFormManagerForUI*)); - - MOCK_CONST_METHOD0(IsMainFrameSecure, bool()); - - MOCK_CONST_METHOD0(GetFieldInfoManager, FieldInfoManager*()); - - MOCK_METHOD0(GetIdentityManager, signin::IdentityManager*()); + MOCK_METHOD(bool, IsIncognito, (), (const, override)); + MOCK_METHOD(autofill::AutofillDownloadManager*, + GetAutofillDownloadManager, + (), + (override)); + MOCK_METHOD(void, UpdateFormManagers, (), (override)); + MOCK_METHOD(void, + AutofillHttpAuth, + (const PasswordForm&, const PasswordFormManagerForUI*), + (override)); + MOCK_METHOD(SyncState, GetPasswordSyncState, (), (const, override)); + MOCK_METHOD(bool, IsCommittedMainFrameSecure, (), (const, override)); + MOCK_METHOD(FieldInfoManager*, GetFieldInfoManager, (), (const, override)); + MOCK_METHOD(signin::IdentityManager*, GetIdentityManager, (), (override)); + MOCK_METHOD(PrefService*, GetPrefs, (), (const, override)); }; void CheckPendingCredentials(const PasswordForm& expected, const PasswordForm& actual) { EXPECT_EQ(expected.signon_realm, actual.signon_realm); - EXPECT_EQ(expected.origin, actual.origin); + EXPECT_EQ(expected.url, actual.url); EXPECT_EQ(expected.action, actual.action); EXPECT_EQ(expected.username_value, actual.username_value); EXPECT_EQ(expected.password_value, actual.password_value); @@ -290,6 +292,11 @@ class PasswordFormManagerTest : public testing::Test, public testing::WithParamInterface<bool> { public: PasswordFormManagerTest() { + pref_service_.registry()->RegisterTimePref( + prefs::kProfileStoreDateLastUsedForFilling, base::Time()); + pref_service_.registry()->RegisterTimePref( + prefs::kAccountStoreDateLastUsedForFilling, base::Time()); + form_manager_->set_wait_for_server_predictions_for_filling(true); GURL origin = GURL("https://accounts.google.com/a/ServiceLoginAuth"); @@ -360,7 +367,7 @@ class PasswordFormManagerTest : public testing::Test, submitted_non_password_form_.fields[kUsernameFieldIndex].value = ASCIIToUTF16("user1"); - saved_match_.origin = origin; + saved_match_.url = origin; saved_match_.action = action; saved_match_.signon_realm = "https://accounts.google.com/"; saved_match_.username_value = ASCIIToUTF16("test@gmail.com"); @@ -372,7 +379,7 @@ class PasswordFormManagerTest : public testing::Test, saved_match_.in_store = PasswordForm::Store::kProfileStore; psl_saved_match_ = saved_match_; - psl_saved_match_.origin = psl_origin; + psl_saved_match_.url = psl_origin; psl_saved_match_.action = psl_action; psl_saved_match_.signon_realm = "https://myaccounts.google.com/"; psl_saved_match_.is_public_suffix_match = true; @@ -393,14 +400,18 @@ class PasswordFormManagerTest : public testing::Test, EXPECT_CALL(client_, GetAutofillDownloadManager()) .WillRepeatedly(Return(&mock_autofill_download_manager_)); - ON_CALL(client_, IsMainFrameSecure()).WillByDefault(Return(true)); + ON_CALL(client_, GetPrefs()).WillByDefault(Return(&pref_service_)); + ON_CALL(client_, IsCommittedMainFrameSecure()).WillByDefault(Return(true)); + ON_CALL(*client_.GetPasswordFeatureManager(), + ShouldShowAccountStorageBubbleUi) + .WillByDefault(Return(true)); ON_CALL(mock_autofill_download_manager_, StartUploadRequest(_, _, _, _, _, _)) .WillByDefault(Return(true)); ON_CALL(*client_.GetPasswordFeatureManager(), GetDefaultPasswordStore) .WillByDefault(Return(PasswordForm::Store::kProfileStore)); - fetcher_.reset(new FakeFormFetcher()); + fetcher_ = std::make_unique<FakeFormFetcher>(); fetcher_->Fetch(); } @@ -420,6 +431,7 @@ class PasswordFormManagerTest : public testing::Test, base::test::TaskEnvironment task_environment_{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; signin::IdentityTestEnvironment identity_test_env_; + TestingPrefServiceSimple pref_service_; MockPasswordManagerClient client_; MockPasswordManagerDriver driver_; @@ -440,9 +452,9 @@ class PasswordFormManagerTest : public testing::Test, : std::make_unique<PasswordSaveManagerImpl>( std::make_unique<NiceMock<MockFormSaver>>()); - form_manager_.reset(new PasswordFormManager( + form_manager_ = std::make_unique<PasswordFormManager>( &client_, driver_.AsWeakPtr(), observed_form, fetcher_.get(), - std::move(password_save_manager), nullptr)); + std::move(password_save_manager), nullptr); } // Creates PasswordFormManager and sets it to |form_manager_| for @@ -459,9 +471,9 @@ class PasswordFormManagerTest : public testing::Test, std::make_unique<NiceMock<MockFormSaver>>()); fetcher_->set_scheme( PasswordStore::FormDigest(base_auth_observed_form).scheme); - form_manager_.reset(new PasswordFormManager( + form_manager_ = std::make_unique<PasswordFormManager>( &client_, PasswordStore::FormDigest(base_auth_observed_form), - fetcher_.get(), std::move(password_save_manager))); + fetcher_.get(), std::move(password_save_manager)); } void SetNonFederatedAndNotifyFetchCompleted( @@ -492,7 +504,7 @@ TEST_P(PasswordFormManagerTest, DoesManageNoFormTag) { FormData another_form = observed_form_; // Simulate that new input was added by JavaScript. - another_form.fields.push_back(FormFieldData()); + another_form.fields.emplace_back(); EXPECT_TRUE(form_manager_->DoesManage(another_form, &driver_)); // Forms on other drivers are not considered managed. EXPECT_FALSE(form_manager_->DoesManage(another_form, nullptr)); @@ -508,7 +520,7 @@ TEST_P(PasswordFormManagerTest, Autofill) { task_environment_.FastForwardUntilNoTasksRemain(); - EXPECT_EQ(observed_form_.url, fill_data.origin); + EXPECT_EQ(observed_form_.url, fill_data.url); EXPECT_FALSE(fill_data.wait_for_username); EXPECT_EQ(observed_form_.fields[1].name, fill_data.username_field.name); EXPECT_EQ(saved_match_.username_value, fill_data.username_field.value); @@ -559,7 +571,6 @@ TEST_P(PasswordFormManagerTest, AutofillSignUpForm) { #if defined(OS_IOS) EXPECT_EQ(observed_form_.unique_renderer_id, generation_data.form_renderer_id); - EXPECT_EQ(ASCIIToUTF16("password"), generation_data.new_password_element); #else EXPECT_EQ(observed_form_.fields.back().unique_renderer_id, generation_data.new_password_renderer_id); @@ -594,7 +605,6 @@ TEST_P(PasswordFormManagerTest, GenerationOnNewAndConfirmPasswordFields) { #if defined(OS_IOS) EXPECT_EQ(observed_form_.unique_renderer_id, generation_data.form_renderer_id); - EXPECT_EQ(ASCIIToUTF16("password"), generation_data.new_password_element); #else EXPECT_EQ(new_password_render_id, generation_data.new_password_renderer_id); EXPECT_EQ(confirm_password_render_id, @@ -611,7 +621,7 @@ TEST_P(PasswordFormManagerTest, AutofillWithBlacklistedMatch) { task_environment_.FastForwardUntilNoTasksRemain(); - EXPECT_EQ(observed_form_.url, fill_data.origin); + EXPECT_EQ(observed_form_.url, fill_data.url); EXPECT_EQ(saved_match_.username_value, fill_data.username_field.value); EXPECT_EQ(saved_match_.password_value, fill_data.password_field.value); } @@ -770,7 +780,7 @@ TEST_P(PasswordFormManagerTest, CreatePendingCredentialsAlreadySaved) { TEST_P(PasswordFormManagerTest, CreatePendingCredentialsPSLMatchSaved) { PasswordForm expected = saved_match_; - saved_match_.origin = GURL("https://m.accounts.google.com/auth"); + saved_match_.url = GURL("https://m.accounts.google.com/auth"); saved_match_.signon_realm = "https://m.accounts.google.com/"; saved_match_.is_public_suffix_match = true; @@ -925,7 +935,7 @@ TEST_P(PasswordFormManagerTest, SaveNewCredentials) { form_manager_->Save(); std::string expected_signon_realm = submitted_form.url.GetOrigin().spec(); - EXPECT_EQ(submitted_form.url, saved_form.origin); + EXPECT_EQ(submitted_form.url, saved_form.url); EXPECT_EQ(expected_signon_realm, saved_form.signon_realm); EXPECT_EQ(new_username, saved_form.username_value); EXPECT_EQ(new_password, saved_form.password_value); @@ -972,7 +982,7 @@ TEST_P(PasswordFormManagerTest, SavePSLToAlreadySaved) { form_manager_->Save(); - EXPECT_EQ(submitted_form.url, saved_form.origin); + EXPECT_EQ(submitted_form.url, saved_form.url); EXPECT_EQ(GetSignonRealm(submitted_form.url), saved_form.signon_realm); EXPECT_EQ(saved_form.username_value, psl_saved_match_.username_value); EXPECT_EQ(saved_form.password_value, psl_saved_match_.password_value); @@ -1829,7 +1839,7 @@ TEST_P(PasswordFormManagerTest, GenerationUploadOnNoInteraction) { fetcher_->NotifyFetchCompleted(); if (generation_popup_shown) { - form_manager_->SetGenerationElement(ASCIIToUTF16("password")); + form_manager_->SetGenerationElement(FieldRendererId(3)); form_manager_->SetGenerationPopupWasShown(false /*is_manual_generation*/); } EXPECT_TRUE( @@ -1852,7 +1862,7 @@ TEST_P(PasswordFormManagerTest, GenerationUploadOnNeverClicked) { fetcher_->NotifyFetchCompleted(); if (generation_popup_shown) { - form_manager_->SetGenerationElement(ASCIIToUTF16("password")); + form_manager_->SetGenerationElement(FieldRendererId(3)); form_manager_->SetGenerationPopupWasShown(false /*is_manual_generation*/); } EXPECT_TRUE( @@ -1999,7 +2009,7 @@ TEST_P(PasswordFormManagerTest, iOSPresavedGeneratedPassword) { const base::string16 generated_password = ASCIIToUTF16("gen_pw"); // Use different |unique_id| and |name| to test that |unique_id| is taken. password_field.unique_id = password_field.name + ASCIIToUTF16("1"); - const base::string16 generation_element = password_field.unique_id; + FieldRendererId generation_element = password_field.unique_renderer_id; PasswordForm saved_form; EXPECT_CALL(form_saver, Save(_, IsEmpty(), base::string16())) @@ -2015,7 +2025,7 @@ TEST_P(PasswordFormManagerTest, iOSPresavedGeneratedPassword) { EXPECT_CALL(form_saver, UpdateReplace(_, _, base::string16(), _)) .WillOnce(SaveArg<0>(&saved_form)); - form_manager_->UpdateStateOnUserInput(form_to_presave.name, + form_manager_->UpdateStateOnUserInput(form_to_presave.unique_renderer_id, generation_element, changed_password); EXPECT_EQ(username_field.value, saved_form.username_value); EXPECT_EQ(changed_password, saved_form.password_value); @@ -2025,15 +2035,15 @@ TEST_P(PasswordFormManagerTest, iOSUpdateStateWithoutPresaving) { fetcher_->NotifyFetchCompleted(); MockFormSaver& form_saver = MockFormSaver::Get(form_manager_.get()); - const base::string16 password_field = - observed_form_.fields[kPasswordFieldIndex].unique_id; + FieldRendererId password_field = + observed_form_.fields[kPasswordFieldIndex].unique_renderer_id; const base::string16 new_field_value = ASCIIToUTF16("some_password"); // Check that nothing is saved on changing password, in case when there was no // pre-saving. EXPECT_CALL(form_saver, Save(_, _, _)).Times(0); EXPECT_TRUE(form_manager_->UpdateStateOnUserInput( - observed_form_.name, password_field, new_field_value)); + observed_form_.unique_renderer_id, password_field, new_field_value)); EXPECT_EQ(new_field_value, form_manager_->observed_form().fields[kPasswordFieldIndex].value); @@ -2257,7 +2267,7 @@ TEST_P(PasswordFormManagerTest, ProvisinallySavedOnSingleUsernameForm) { &driver_, nullptr)); } -TEST_P(PasswordFormManagerTest, NotMovableToAccountStore) { +TEST_P(PasswordFormManagerTest, NotMovableToAccountStoreWhenBlocked) { const std::string kEmail = "email@gmail.com"; const std::string kGaiaId = signin::GetTestGaiaIdForEmail(kEmail); @@ -2277,12 +2287,9 @@ TEST_P(PasswordFormManagerTest, NotMovableToAccountStore) { EXPECT_TRUE( form_manager_->ProvisionallySave(submitted_form_, &driver_, nullptr)); + // Even with |kEmail| is signed in, credentials should NOT be movable. ON_CALL(client_, GetIdentityManager()) .WillByDefault(Return(identity_test_env_.identity_manager())); - // If not user is signed in, the credentials should NOT be movable. - EXPECT_FALSE(form_manager_->IsMovableToAccountStore()); - - // When |kEmail| is signed in, credentials should NOT be movable. identity_test_env_.SetPrimaryAccount(kEmail); EXPECT_FALSE(form_manager_->IsMovableToAccountStore()); } @@ -2356,7 +2363,8 @@ class MockPasswordSaveManager : public PasswordSaveManager { std::unique_ptr<PasswordSaveManager> Clone() override { return std::make_unique<MockPasswordSaveManager>(); } - MOCK_METHOD0(MoveCredentialsToAccountStore, void()); + MOCK_METHOD1(MoveCredentialsToAccountStore, + void(metrics_util::MoveToAccountStoreTrigger)); MOCK_METHOD1(BlockMovingToAccountStoreFor, void(const autofill::GaiaIdHash&)); private: @@ -2378,9 +2386,9 @@ class PasswordFormManagerTestWithMockedSaver : public PasswordFormManagerTest { std::make_unique<NiceMock<MockPasswordSaveManager>>(); mock_password_save_manager_ = mock_password_save_manager.get(); EXPECT_CALL(*mock_password_save_manager_, Init(_, _, _, _)); - form_manager_.reset(new PasswordFormManager( + form_manager_ = std::make_unique<PasswordFormManager>( &client_, driver_.AsWeakPtr(), observed_form, fetcher_.get(), - std::move(mock_password_save_manager), nullptr)); + std::move(mock_password_save_manager), nullptr); } // Creates PasswordFormManager and sets it to |form_manager_| for @@ -2393,9 +2401,9 @@ class PasswordFormManagerTestWithMockedSaver : public PasswordFormManagerTest { std::make_unique<NiceMock<MockPasswordSaveManager>>(); mock_password_save_manager_ = mock_password_save_manager.get(); EXPECT_CALL(*mock_password_save_manager_, Init(_, _, _, _)); - form_manager_.reset(new PasswordFormManager( + form_manager_ = std::make_unique<PasswordFormManager>( &client_, PasswordStore::FormDigest(base_auth_observed_form), - fetcher_.get(), std::move(mock_password_save_manager))); + fetcher_.get(), std::move(mock_password_save_manager)); } private: @@ -2431,7 +2439,7 @@ TEST_F(PasswordFormManagerTestWithMockedSaver, SaveCredentials) { EXPECT_CALL(client_, UpdateFormManagers()); form_manager_->Save(); std::string expected_signon_realm = submitted_form.url.GetOrigin().spec(); - EXPECT_EQ(submitted_form.url, updated_form.origin); + EXPECT_EQ(submitted_form.url, updated_form.url); EXPECT_EQ(expected_signon_realm, updated_form.signon_realm); EXPECT_EQ(new_username, updated_form.username_value); EXPECT_EQ(new_password, updated_form.password_value); @@ -2543,7 +2551,12 @@ TEST_F(PasswordFormManagerTestWithMockedSaver, PermanentlyBlacklist) { } TEST_F(PasswordFormManagerTestWithMockedSaver, MoveCredentialsToAccountStore) { - EXPECT_CALL(*mock_password_save_manager(), MoveCredentialsToAccountStore()); + ON_CALL(*client_.GetPasswordFeatureManager(), IsOptedInForAccountStorage) + .WillByDefault(Return(true)); + EXPECT_CALL(*mock_password_save_manager(), + MoveCredentialsToAccountStore( + metrics_util::MoveToAccountStoreTrigger:: + kSuccessfulLoginWithProfileStorePassword)); form_manager_->MoveCredentialsToAccountStore(); } diff --git a/chromium/components/password_manager/core/browser/password_form_metrics_recorder.cc b/chromium/components/password_manager/core/browser/password_form_metrics_recorder.cc index dfd469f5dbf..0bea0c0a8a4 100644 --- a/chromium/components/password_manager/core/browser/password_form_metrics_recorder.cc +++ b/chromium/components/password_manager/core/browser/password_form_metrics_recorder.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/browser/password_form_metrics_recorder.h" +#include <stdint.h> + #include <algorithm> #include "base/check_op.h" @@ -13,12 +15,15 @@ #include "base/notreached.h" #include "base/numerics/safe_conversions.h" #include "base/stl_util.h" +#include "base/time/default_clock.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/password_generation_util.h" #include "components/password_manager/core/browser/form_fetcher.h" #include "components/password_manager/core/browser/password_bubble_experiment.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/statistics_table.h" +#include "components/password_manager/core/common/password_manager_pref_names.h" +#include "components/prefs/pref_service.h" using autofill::FieldPropertiesFlags; using autofill::FormData; @@ -183,17 +188,34 @@ bool BlacklistedBySmartBubble( return false; } +PasswordFormMetricsRecorder::FillingSource ComputeFillingSource( + bool filled_from_profile_store, + bool filled_from_account_store) { + using FillingSource = PasswordFormMetricsRecorder::FillingSource; + if (filled_from_profile_store) { + if (filled_from_account_store) + return FillingSource::kFilledFromBothStores; + return FillingSource::kFilledFromProfileStore; + } + if (filled_from_account_store) + return FillingSource::kFilledFromAccountStore; + return FillingSource::kNotFilled; +} + } // namespace PasswordFormMetricsRecorder::PasswordFormMetricsRecorder( bool is_main_frame_secure, - ukm::SourceId source_id) - : is_main_frame_secure_(is_main_frame_secure), + ukm::SourceId source_id, + PrefService* pref_service) + : clock_(base::DefaultClock::GetInstance()), + is_main_frame_secure_(is_main_frame_secure), source_id_(source_id), - ukm_entry_builder_(source_id) {} + ukm_entry_builder_(source_id), + pref_service_(pref_service) {} PasswordFormMetricsRecorder::~PasswordFormMetricsRecorder() { - if (submit_result_ == kSubmitResultNotSubmitted) { + if (submit_result_ == SubmitResult::kNotSubmitted) { if (HasGeneratedPassword(generated_password_status_)) { metrics_util::LogPasswordGenerationSubmissionEvent( metrics_util::PASSWORD_NOT_SUBMITTED); @@ -204,15 +226,17 @@ PasswordFormMetricsRecorder::~PasswordFormMetricsRecorder() { ukm_entry_builder_.SetSubmission_Observed(0 /*false*/); } - if (submitted_form_type_ != kSubmittedFormTypeUnspecified) { + if (submitted_form_type_ != SubmittedFormType::kUnspecified) { UMA_HISTOGRAM_ENUMERATION("PasswordManager.SubmittedFormType", - submitted_form_type_, kSubmittedFormTypeMax); + submitted_form_type_, SubmittedFormType::kCount); if (!is_main_frame_secure_) { UMA_HISTOGRAM_ENUMERATION("PasswordManager.SubmittedNonSecureFormType", - submitted_form_type_, kSubmittedFormTypeMax); + submitted_form_type_, + SubmittedFormType::kCount); } - ukm_entry_builder_.SetSubmission_SubmittedFormType(submitted_form_type_); + ukm_entry_builder_.SetSubmission_SubmittedFormType( + static_cast<int64_t>(submitted_form_type_)); } ukm_entry_builder_.SetUpdating_Prompt_Shown(update_prompt_shown_); @@ -273,7 +297,7 @@ PasswordFormMetricsRecorder::~PasswordFormMetricsRecorder() { ukm_entry_builder_.SetDynamicFormChanges(*form_changes_bitmask_); } - if (submit_result_ == kSubmitResultPassed && filling_assistance_) { + if (submit_result_ == SubmitResult::kPassed && filling_assistance_) { FillingAssistance filling_assistance = *filling_assistance_; UMA_HISTOGRAM_ENUMERATION("PasswordManager.FillingAssistance", filling_assistance); @@ -304,10 +328,45 @@ PasswordFormMetricsRecorder::~PasswordFormMetricsRecorder() { if (filling_source_) { base::UmaHistogramEnumeration("PasswordManager.FillingSource", *filling_source_); + + // Update the "last used for filling" timestamp for the affected store(s). + base::Time now = clock_->Now(); + if (*filling_source_ == FillingSource::kFilledFromProfileStore || + *filling_source_ == FillingSource::kFilledFromBothStores) { + pref_service_->SetTime(prefs::kProfileStoreDateLastUsedForFilling, now); + } + if (*filling_source_ == FillingSource::kFilledFromAccountStore || + *filling_source_ == FillingSource::kFilledFromBothStores) { + pref_service_->SetTime(prefs::kAccountStoreDateLastUsedForFilling, now); + } + + // Determine which of the store(s) were used in the last 7/28 days. + base::Time profile_store_last_use = + pref_service_->GetTime(prefs::kProfileStoreDateLastUsedForFilling); + base::Time account_store_last_use = + pref_service_->GetTime(prefs::kAccountStoreDateLastUsedForFilling); + + bool was_profile_store_used_in_last_7_days = + (now - profile_store_last_use) < base::TimeDelta::FromDays(7); + bool was_account_store_used_in_last_7_days = + (now - account_store_last_use) < base::TimeDelta::FromDays(7); + base::UmaHistogramEnumeration( + "PasswordManager.StoresUsedForFillingInLast7Days", + ComputeFillingSource(was_profile_store_used_in_last_7_days, + was_account_store_used_in_last_7_days)); + + bool was_profile_store_used_in_last_28_days = + (now - profile_store_last_use) < base::TimeDelta::FromDays(28); + bool was_account_store_used_in_last_28_days = + (now - account_store_last_use) < base::TimeDelta::FromDays(28); + base::UmaHistogramEnumeration( + "PasswordManager.StoresUsedForFillingInLast28Days", + ComputeFillingSource(was_profile_store_used_in_last_28_days, + was_account_store_used_in_last_28_days)); } } - if (submit_result_ == kSubmitResultPassed && js_only_input_) { + if (submit_result_ == SubmitResult::kPassed && js_only_input_) { UMA_HISTOGRAM_ENUMERATION( "PasswordManager.JavaScriptOnlyValueInSubmittedForm", *js_only_input_); } @@ -339,7 +398,7 @@ void PasswordFormMetricsRecorder::SetManagerAction( } void PasswordFormMetricsRecorder::LogSubmitPassed() { - if (submit_result_ != kSubmitResultFailed) { + if (submit_result_ != SubmitResult::kFailed) { if (HasGeneratedPassword(generated_password_status_)) { metrics_util::LogPasswordGenerationSubmissionEvent( metrics_util::PASSWORD_SUBMITTED); @@ -350,8 +409,9 @@ void PasswordFormMetricsRecorder::LogSubmitPassed() { } base::RecordAction(base::UserMetricsAction("PasswordManager_LoginPassed")); ukm_entry_builder_.SetSubmission_Observed(1 /*true*/); - ukm_entry_builder_.SetSubmission_SubmissionResult(kSubmitResultPassed); - submit_result_ = kSubmitResultPassed; + ukm_entry_builder_.SetSubmission_SubmissionResult( + static_cast<int64_t>(SubmitResult::kPassed)); + submit_result_ = SubmitResult::kPassed; } void PasswordFormMetricsRecorder::LogSubmitFailed() { @@ -364,8 +424,9 @@ void PasswordFormMetricsRecorder::LogSubmitFailed() { } base::RecordAction(base::UserMetricsAction("PasswordManager_LoginFailed")); ukm_entry_builder_.SetSubmission_Observed(1 /*true*/); - ukm_entry_builder_.SetSubmission_SubmissionResult(kSubmitResultFailed); - submit_result_ = kSubmitResultFailed; + ukm_entry_builder_.SetSubmission_SubmissionResult( + static_cast<int64_t>(SubmitResult::kFailed)); + submit_result_ = SubmitResult::kFailed; } void PasswordFormMetricsRecorder::SetPasswordGenerationPopupShown( @@ -503,15 +564,10 @@ void PasswordFormMetricsRecorder::CalculateFillingAssistanceMetric( // At this point, the password was filled from at least one of the two stores, // so compute the filling source now. - if (username_password_state.password_exists_in_profile_store && - username_password_state.password_exists_in_account_store) { - filling_source_ = FillingSource::kFilledFromBothStores; - } else if (username_password_state.password_exists_in_profile_store) { - filling_source_ = FillingSource::kFilledFromProfileStore; - } else { - DCHECK(username_password_state.password_exists_in_account_store); - filling_source_ = FillingSource::kFilledFromAccountStore; - } + filling_source_ = ComputeFillingSource( + username_password_state.password_exists_in_profile_store, + username_password_state.password_exists_in_account_store); + DCHECK_NE(*filling_source_, FillingSource::kNotFilled); if (username_password_state.saved_username_typed) { filling_assistance_ = FillingAssistance::kUsernameTypedPasswordFilled; diff --git a/chromium/components/password_manager/core/browser/password_form_metrics_recorder.h b/chromium/components/password_manager/core/browser/password_form_metrics_recorder.h index 4431fd903d8..5c9e96c2e25 100644 --- a/chromium/components/password_manager/core/browser/password_form_metrics_recorder.h +++ b/chromium/components/password_manager/core/browser/password_form_metrics_recorder.h @@ -15,6 +15,7 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/optional.h" +#include "base/time/clock.h" #include "components/autofill/core/common/mojom/autofill_types.mojom.h" #include "components/autofill/core/common/password_form.h" #include "components/autofill/core/common/signatures.h" @@ -23,6 +24,8 @@ #include "services/metrics/public/cpp/ukm_recorder.h" #include "url/gurl.h" +class PrefService; + namespace autofill { struct FormData; } @@ -50,7 +53,8 @@ class PasswordFormMetricsRecorder // Records UKM metrics and reports them on destruction. The |source_id| is // the ID of the WebContents document that the forms belong to. PasswordFormMetricsRecorder(bool is_main_frame_secure, - ukm::SourceId source_id); + ukm::SourceId source_id, + PrefService* pref_service); // ManagerAction - What does the PasswordFormManager do with this form? Either // it fills it, or it doesn't. If it doesn't fill it, that's either @@ -65,11 +69,15 @@ class PasswordFormMetricsRecorder }; // Result - What happens to the form? - enum SubmitResult { - kSubmitResultNotSubmitted = 0, - kSubmitResultFailed, - kSubmitResultPassed, - kSubmitResultMax + // + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + // + // Needs to stay in sync with PasswordFormSubmissionResult in enums.xml. + enum class SubmitResult { + kNotSubmitted = 0, + kFailed = 1, + kPassed = 2, }; // Whether the password manager filled a credential on a form. @@ -85,37 +93,51 @@ class PasswordFormMetricsRecorder kManagerFillEventAutofilled }; - // What the form is used for. kSubmittedFormTypeUnspecified is only set before - // the SetSubmittedFormType() is called, and should never be actually + // What the form is used for. SubmittedFormType::kUnspecified is only set + // before the SetSubmittedFormType() is called, and should never be actually // uploaded. - enum SubmittedFormType { - kSubmittedFormTypeLogin, - kSubmittedFormTypeLoginNoUsername, - kSubmittedFormTypeChangePasswordEnabled, - kSubmittedFormTypeChangePasswordDisabled, - kSubmittedFormTypeChangePasswordNoUsername, - kSubmittedFormTypeSignup, - kSubmittedFormTypeSignupNoUsername, - kSubmittedFormTypeLoginAndSignup, - kSubmittedFormTypeUnspecified, - kSubmittedFormTypeMax + // + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + // + // Needs to stay in sync with PasswordFormType in enums.xml. + enum class SubmittedFormType { + kLogin = 0, + kLoginNoUsername = 1, + kChangePasswordEnabled = 2, + kChangePasswordDisabled = 3, + kChangePasswordNoUsername = 4, + kSignup = 5, + kSignupNoUsername = 6, + kLoginAndSignup = 7, + kUnspecified = 8, + kCount = 9, }; // The reason why a password bubble was shown on the screen. + // + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + // + // Needs to stay in sync with PasswordBubbleTrigger in enums.xml. enum class BubbleTrigger { kUnknown = 0, // The password manager suggests the user to save a password and asks for // confirmation. - kPasswordManagerSuggestionAutomatic, - kPasswordManagerSuggestionManual, + kPasswordManagerSuggestionAutomatic = 1, + kPasswordManagerSuggestionManual = 2, // The site asked the user to save a password via the credential management // API. - kCredentialManagementAPIAutomatic, - kCredentialManagementAPIManual, - kMax, + kCredentialManagementAPIAutomatic = 3, + kCredentialManagementAPIManual = 4, }; // The reason why a password bubble was dismissed. + // + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + // + // Needs to stay in sync with PasswordBubbleDismissalReason in enums.xml. enum class BubbleDismissalReason { kUnknown = 0, kAccepted = 1, @@ -138,32 +160,12 @@ class PasswordFormMetricsRecorder kCorrectedUsernameInForm = 200, }; - // Result of comparing of the old and new form parsing for filling. - enum class ParsingComparisonResult { - kSame, - kDifferent, - // Old and new parsers use different identification mechanism for unnamed - // fields, so the difference in parsing of anonymous fields is expected. - kAnonymousFields, - kMax - }; - - // Result of comparing of the old and new form parsing for saving. Multiple - // values are meant to be combined and reported in a single number as a - // bitmask. - enum class ParsingOnSavingDifference { - // Different fields were identified for username or password. - kFields = 1 << 0, - // Signon_realms are different. - kSignonRealm = 1 << 1, - // One password form manager wants to update, the other to save as new. - kNewLoginStatus = 1 << 2, - // One password form manager thinks the password is generated, the other - // does not. - kGenerated = 1 << 3, - }; - // Indicator whether the user has seen a password generation popup and why. + // + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + // + // Needs to stay in sync with PasswordGenerationPopupShown in enums.xml. enum class PasswordGenerationPopupShown { kNotShown = 0, kShownAutomatically = 1, @@ -394,6 +396,8 @@ class PasswordFormMetricsRecorder username_updated_in_bubble_ = value; } + void set_clock_for_testing(base::Clock* clock) { clock_ = clock; } + private: friend class base::RefCounted<PasswordFormMetricsRecorder>; @@ -412,7 +416,11 @@ class PasswordFormMetricsRecorder // RecordUkmMetric. ~PasswordFormMetricsRecorder(); - // True if the main frame's visible URL, at the time this PasswordFormManager + // Not owned. Points to base::DefaultClock::GetInstance() by default, but can + // be overridden for testing. + base::Clock* clock_; + + // True if the main frame's committed URL, at the time PasswordFormManager // was created, is secure. const bool is_main_frame_secure_; @@ -441,12 +449,12 @@ class PasswordFormMetricsRecorder // the user with this form, and the result. They are combined and // recorded in UMA when the PasswordFormMetricsRecorder is destroyed. ManagerAction manager_action_ = kManagerActionNone; - SubmitResult submit_result_ = kSubmitResultNotSubmitted; + SubmitResult submit_result_ = SubmitResult::kNotSubmitted; // Form type of the form that the PasswordFormManager is managing. Set after // submission as the classification of the form can change depending on what // data the user has entered. - SubmittedFormType submitted_form_type_ = kSubmittedFormTypeUnspecified; + SubmittedFormType submitted_form_type_ = SubmittedFormType::kUnspecified; // The UKM SourceId of the document the form belongs to. ukm::SourceId source_id_; @@ -454,6 +462,8 @@ class PasswordFormMetricsRecorder // Holds URL keyed metrics (UKMs) to be recorded on destruction. ukm::builders::PasswordForm ukm_entry_builder_; + PrefService* const pref_service_; + // Counter for DetailedUserActions observed during the lifetime of a // PasswordFormManager. Reported upon destruction. std::map<DetailedUserAction, int64_t> detailed_user_actions_counts_; diff --git a/chromium/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc b/chromium/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc index d2eeb171933..c2b9d3e771f 100644 --- a/chromium/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc @@ -4,17 +4,22 @@ #include "components/password_manager/core/browser/password_form_metrics_recorder.h" +#include <stdint.h> + #include "base/metrics/metrics_hashes.h" #include "base/notreached.h" #include "base/strings/string16.h" #include "base/strings/utf_string_conversions.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/user_action_tester.h" +#include "base/test/simple_test_clock.h" #include "base/test/task_environment.h" #include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_field_data.h" +#include "components/password_manager/core/browser/password_manager.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/statistics_table.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" #include "components/ukm/test_ukm_recorder.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" @@ -40,9 +45,9 @@ using UkmEntry = ukm::builders::PasswordForm; // Create a UkmEntryBuilder with kTestSourceId. scoped_refptr<PasswordFormMetricsRecorder> CreatePasswordFormMetricsRecorder( bool is_main_frame_secure, - ukm::TestUkmRecorder* test_ukm_recorder) { - return base::MakeRefCounted<PasswordFormMetricsRecorder>(is_main_frame_secure, - kTestSourceId); + PrefService* pref_service) { + return base::MakeRefCounted<PasswordFormMetricsRecorder>( + is_main_frame_secure, kTestSourceId, pref_service); } // TODO(crbug.com/738921) Replace this with generalized infrastructure. @@ -71,28 +76,31 @@ void ExpectUkmValueCount(ukm::TestUkmRecorder* test_ukm_recorder, // Test the metrics recorded around password generation and the user's // interaction with the offer to generate passwords. TEST(PasswordFormMetricsRecorder, Generation) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + static constexpr struct { bool generation_available; bool has_generated_password; PasswordFormMetricsRecorder::SubmitResult submission; } kTests[] = { - {false, false, PasswordFormMetricsRecorder::kSubmitResultNotSubmitted}, - {true, false, PasswordFormMetricsRecorder::kSubmitResultNotSubmitted}, - {true, true, PasswordFormMetricsRecorder::kSubmitResultNotSubmitted}, - {false, false, PasswordFormMetricsRecorder::kSubmitResultFailed}, - {true, false, PasswordFormMetricsRecorder::kSubmitResultFailed}, - {true, true, PasswordFormMetricsRecorder::kSubmitResultFailed}, - {false, false, PasswordFormMetricsRecorder::kSubmitResultPassed}, - {true, false, PasswordFormMetricsRecorder::kSubmitResultPassed}, - {true, true, PasswordFormMetricsRecorder::kSubmitResultPassed}, + {false, false, PasswordFormMetricsRecorder::SubmitResult::kNotSubmitted}, + {true, false, PasswordFormMetricsRecorder::SubmitResult::kNotSubmitted}, + {true, true, PasswordFormMetricsRecorder::SubmitResult::kNotSubmitted}, + {false, false, PasswordFormMetricsRecorder::SubmitResult::kFailed}, + {true, false, PasswordFormMetricsRecorder::SubmitResult::kFailed}, + {true, true, PasswordFormMetricsRecorder::SubmitResult::kFailed}, + {false, false, PasswordFormMetricsRecorder::SubmitResult::kPassed}, + {true, false, PasswordFormMetricsRecorder::SubmitResult::kPassed}, + {true, true, PasswordFormMetricsRecorder::SubmitResult::kPassed}, }; for (const auto& test : kTests) { SCOPED_TRACE(testing::Message() << "generation_available=" << test.generation_available << ", has_generated_password=" << test.has_generated_password - << ", submission=" << test.submission); + << ", submission=" << static_cast<int64_t>(test.submission)); ukm::TestAutoSetUkmRecorder test_ukm_recorder; base::HistogramTester histogram_tester; @@ -102,7 +110,7 @@ TEST(PasswordFormMetricsRecorder, Generation) { // on destruction. { auto recorder = CreatePasswordFormMetricsRecorder( - /*is_main_frame_secure*/ true, &test_ukm_recorder); + /*is_main_frame_secure*/ true, &pref_service); if (test.generation_available) recorder->MarkGenerationAvailable(); if (test.has_generated_password) { @@ -111,96 +119,97 @@ TEST(PasswordFormMetricsRecorder, Generation) { kPasswordAccepted); } switch (test.submission) { - case PasswordFormMetricsRecorder::kSubmitResultNotSubmitted: + case PasswordFormMetricsRecorder::SubmitResult::kNotSubmitted: // Do nothing. break; - case PasswordFormMetricsRecorder::kSubmitResultFailed: + case PasswordFormMetricsRecorder::SubmitResult::kFailed: recorder->LogSubmitFailed(); break; - case PasswordFormMetricsRecorder::kSubmitResultPassed: + case PasswordFormMetricsRecorder::SubmitResult::kPassed: recorder->LogSubmitPassed(); break; - case PasswordFormMetricsRecorder::kSubmitResultMax: - NOTREACHED(); } } ExpectUkmValueCount( &test_ukm_recorder, UkmEntry::kSubmission_ObservedName, test.submission != - PasswordFormMetricsRecorder::kSubmitResultNotSubmitted + PasswordFormMetricsRecorder::SubmitResult::kNotSubmitted ? 1 : 0, 1); int expected_login_failed = - test.submission == PasswordFormMetricsRecorder::kSubmitResultFailed ? 1 - : 0; + test.submission == PasswordFormMetricsRecorder::SubmitResult::kFailed + ? 1 + : 0; EXPECT_EQ(expected_login_failed, user_action_tester.GetActionCount("PasswordManager_LoginFailed")); ExpectUkmValueCount(&test_ukm_recorder, UkmEntry::kSubmission_SubmissionResultName, - PasswordFormMetricsRecorder::kSubmitResultFailed, + static_cast<int64_t>( + PasswordFormMetricsRecorder::SubmitResult::kFailed), expected_login_failed); int expected_login_passed = - test.submission == PasswordFormMetricsRecorder::kSubmitResultPassed ? 1 - : 0; + test.submission == PasswordFormMetricsRecorder::SubmitResult::kPassed + ? 1 + : 0; EXPECT_EQ(expected_login_passed, user_action_tester.GetActionCount("PasswordManager_LoginPassed")); ExpectUkmValueCount(&test_ukm_recorder, UkmEntry::kSubmission_SubmissionResultName, - PasswordFormMetricsRecorder::kSubmitResultPassed, + static_cast<int64_t>( + PasswordFormMetricsRecorder::SubmitResult::kPassed), expected_login_passed); if (test.has_generated_password) { switch (test.submission) { - case PasswordFormMetricsRecorder::kSubmitResultNotSubmitted: + case PasswordFormMetricsRecorder::SubmitResult::kNotSubmitted: histogram_tester.ExpectBucketCount( "PasswordGeneration.SubmissionEvent", metrics_util::PASSWORD_NOT_SUBMITTED, 1); break; - case PasswordFormMetricsRecorder::kSubmitResultFailed: + case PasswordFormMetricsRecorder::SubmitResult::kFailed: histogram_tester.ExpectBucketCount( "PasswordGeneration.SubmissionEvent", metrics_util::GENERATED_PASSWORD_FORCE_SAVED, 1); break; - case PasswordFormMetricsRecorder::kSubmitResultPassed: + case PasswordFormMetricsRecorder::SubmitResult::kPassed: histogram_tester.ExpectBucketCount( "PasswordGeneration.SubmissionEvent", metrics_util::PASSWORD_SUBMITTED, 1); break; - case PasswordFormMetricsRecorder::kSubmitResultMax: - NOTREACHED(); } } if (!test.has_generated_password && test.generation_available) { switch (test.submission) { - case PasswordFormMetricsRecorder::kSubmitResultNotSubmitted: + case PasswordFormMetricsRecorder::SubmitResult::kNotSubmitted: histogram_tester.ExpectBucketCount( "PasswordGeneration.SubmissionAvailableEvent", metrics_util::PASSWORD_NOT_SUBMITTED, 1); break; - case PasswordFormMetricsRecorder::kSubmitResultFailed: + case PasswordFormMetricsRecorder::SubmitResult::kFailed: histogram_tester.ExpectBucketCount( "PasswordGeneration.SubmissionAvailableEvent", metrics_util::PASSWORD_SUBMISSION_FAILED, 1); break; - case PasswordFormMetricsRecorder::kSubmitResultPassed: + case PasswordFormMetricsRecorder::SubmitResult::kPassed: histogram_tester.ExpectBucketCount( "PasswordGeneration.SubmissionAvailableEvent", metrics_util::PASSWORD_SUBMITTED, 1); break; - case PasswordFormMetricsRecorder::kSubmitResultMax: - NOTREACHED(); } } } } TEST(PasswordFormMetricsRecorder, SubmittedFormType) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + static constexpr struct { // Stimuli: bool is_main_frame_secure; @@ -211,15 +220,17 @@ TEST(PasswordFormMetricsRecorder, SubmittedFormType) { // Expectation for PasswordManager.SubmittedNonSecureFormType: int expected_submitted_non_secure_form_type; } kTests[] = { - {false, PasswordFormMetricsRecorder::kSubmittedFormTypeUnspecified, 0, 0}, - {true, PasswordFormMetricsRecorder::kSubmittedFormTypeUnspecified, 0, 0}, - {false, PasswordFormMetricsRecorder::kSubmittedFormTypeLogin, 1, 1}, - {true, PasswordFormMetricsRecorder::kSubmittedFormTypeLogin, 1, 0}, + {false, PasswordFormMetricsRecorder::SubmittedFormType::kUnspecified, 0, + 0}, + {true, PasswordFormMetricsRecorder::SubmittedFormType::kUnspecified, 0, + 0}, + {false, PasswordFormMetricsRecorder::SubmittedFormType::kLogin, 1, 1}, + {true, PasswordFormMetricsRecorder::SubmittedFormType::kLogin, 1, 0}, }; for (const auto& test : kTests) { SCOPED_TRACE(testing::Message() << "is_main_frame_secure=" << test.is_main_frame_secure - << ", form_type=" << test.form_type); + << ", form_type=" << static_cast<int64_t>(test.form_type)); ukm::TestAutoSetUkmRecorder test_ukm_recorder; base::HistogramTester histogram_tester; @@ -228,15 +239,15 @@ TEST(PasswordFormMetricsRecorder, SubmittedFormType) { // on destruction. { auto recorder = CreatePasswordFormMetricsRecorder( - test.is_main_frame_secure, &test_ukm_recorder); + test.is_main_frame_secure, &pref_service); recorder->SetSubmittedFormType(test.form_type); } if (test.form_type != - PasswordFormMetricsRecorder::kSubmittedFormTypeUnspecified) { + PasswordFormMetricsRecorder::SubmittedFormType::kUnspecified) { ExpectUkmValueCount(&test_ukm_recorder, UkmEntry::kSubmission_SubmittedFormTypeName, - test.form_type, 1); + static_cast<int64_t>(test.form_type), 1); } if (test.expected_submitted_form_type) { @@ -259,7 +270,10 @@ TEST(PasswordFormMetricsRecorder, SubmittedFormType) { } TEST(PasswordFormMetricsRecorder, RecordPasswordBubbleShown) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + using Trigger = PasswordFormMetricsRecorder::BubbleTrigger; static constexpr struct { // Stimuli: @@ -322,7 +336,7 @@ TEST(PasswordFormMetricsRecorder, RecordPasswordBubbleShown) { ukm::TestAutoSetUkmRecorder test_ukm_recorder; { auto recorder = CreatePasswordFormMetricsRecorder( - true /*is_main_frame_secure*/, &test_ukm_recorder); + true /*is_main_frame_secure*/, &pref_service); recorder->RecordPasswordBubbleShown(test.credential_source_type, test.display_disposition); } @@ -354,7 +368,10 @@ TEST(PasswordFormMetricsRecorder, RecordPasswordBubbleShown) { } TEST(PasswordFormMetricsRecorder, RecordUIDismissalReason) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + static constexpr struct { // Stimuli: metrics_util::UIDisplayDisposition display_disposition; @@ -385,7 +402,7 @@ TEST(PasswordFormMetricsRecorder, RecordUIDismissalReason) { ukm::TestAutoSetUkmRecorder test_ukm_recorder; { auto recorder = CreatePasswordFormMetricsRecorder( - true /*is_main_frame_secure*/, &test_ukm_recorder); + true /*is_main_frame_secure*/, &pref_service); recorder->RecordPasswordBubbleShown( metrics_util::CredentialSourceType::kPasswordManager, test.display_disposition); @@ -406,14 +423,17 @@ TEST(PasswordFormMetricsRecorder, RecordUIDismissalReason) { // Verify that it is ok to open and close the password bubble more than once // and still get accurate metrics. TEST(PasswordFormMetricsRecorder, SequencesOfBubbles) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + using BubbleDismissalReason = PasswordFormMetricsRecorder::BubbleDismissalReason; using BubbleTrigger = PasswordFormMetricsRecorder::BubbleTrigger; ukm::TestAutoSetUkmRecorder test_ukm_recorder; { auto recorder = CreatePasswordFormMetricsRecorder( - true /*is_main_frame_secure*/, &test_ukm_recorder); + true /*is_main_frame_secure*/, &pref_service); // Open and confirm an automatically triggered saving prompt. recorder->RecordPasswordBubbleShown( metrics_util::CredentialSourceType::kPasswordManager, @@ -453,12 +473,15 @@ TEST(PasswordFormMetricsRecorder, SequencesOfBubbles) { // Verify that one-time actions are only recorded once per life-cycle of a // PasswordFormMetricsRecorder. TEST(PasswordFormMetricsRecorder, RecordDetailedUserAction) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + using Action = PasswordFormMetricsRecorder::DetailedUserAction; ukm::TestAutoSetUkmRecorder test_ukm_recorder; { auto recorder = CreatePasswordFormMetricsRecorder( - true /*is_main_frame_secure*/, &test_ukm_recorder); + true /*is_main_frame_secure*/, &pref_service); recorder->RecordDetailedUserAction(Action::kCorrectedUsernameInForm); recorder->RecordDetailedUserAction(Action::kCorrectedUsernameInForm); recorder->RecordDetailedUserAction(Action::kEditedUsernameInBubble); @@ -479,7 +502,9 @@ TEST(PasswordFormMetricsRecorder, RecordDetailedUserAction) { // Verify that the the mapping is correct and that metrics are actually // recorded. TEST(PasswordFormMetricsRecorder, RecordShowManualFallbackForSaving) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); struct { bool has_generated_password; bool is_update; @@ -494,7 +519,7 @@ TEST(PasswordFormMetricsRecorder, RecordShowManualFallbackForSaving) { ukm::TestAutoSetUkmRecorder test_ukm_recorder; { auto recorder = CreatePasswordFormMetricsRecorder( - true /*is_main_frame_secure*/, &test_ukm_recorder); + true /*is_main_frame_secure*/, &pref_service); recorder->RecordShowManualFallbackForSaving(test.has_generated_password, test.is_update); } @@ -509,11 +534,13 @@ TEST(PasswordFormMetricsRecorder, RecordShowManualFallbackForSaving) { // Verify that no 0 is recorded if now fallback icon is shown. TEST(PasswordFormMetricsRecorder, NoRecordShowManualFallbackForSaving) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); ukm::TestAutoSetUkmRecorder test_ukm_recorder; { auto recorder = CreatePasswordFormMetricsRecorder( - true /*is_main_frame_secure*/, &test_ukm_recorder); + true /*is_main_frame_secure*/, &pref_service); } auto entries = test_ukm_recorder.GetEntriesByName(UkmEntry::kEntryName); ASSERT_EQ(1u, entries.size()); @@ -524,11 +551,13 @@ TEST(PasswordFormMetricsRecorder, NoRecordShowManualFallbackForSaving) { // Verify that only the latest value is recorded TEST(PasswordFormMetricsRecorder, RecordShowManualFallbackForSavingLatestOnly) { - base::test::TaskEnvironment task_environment_; + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); ukm::TestAutoSetUkmRecorder test_ukm_recorder; { auto recorder = CreatePasswordFormMetricsRecorder( - true /*is_main_frame_secure*/, &test_ukm_recorder); + true /*is_main_frame_secure*/, &pref_service); recorder->RecordShowManualFallbackForSaving(true, false); recorder->RecordShowManualFallbackForSaving(true, true); } @@ -541,17 +570,21 @@ TEST(PasswordFormMetricsRecorder, RecordShowManualFallbackForSavingLatestOnly) { } TEST(PasswordFormMetricsRecorder, FormChangeBitmapNoMetricRecorded) { + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); base::HistogramTester histogram_tester; - auto recorder = - CreatePasswordFormMetricsRecorder(true /*is_main_frame_secure*/, nullptr); + auto recorder = CreatePasswordFormMetricsRecorder( + true /*is_main_frame_secure*/, &pref_service); recorder.reset(); histogram_tester.ExpectTotalCount("PasswordManager.DynamicFormChanges", 0); } TEST(PasswordFormMetricsRecorder, FormChangeBitmapRecordedOnce) { + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); base::HistogramTester histogram_tester; - auto recorder = - CreatePasswordFormMetricsRecorder(true /*is_main_frame_secure*/, nullptr); + auto recorder = CreatePasswordFormMetricsRecorder( + true /*is_main_frame_secure*/, &pref_service); recorder->RecordFormChangeBitmask(PasswordFormMetricsRecorder::kFieldsNumber); recorder.reset(); histogram_tester.ExpectUniqueSample("PasswordManager.DynamicFormChanges", @@ -559,9 +592,11 @@ TEST(PasswordFormMetricsRecorder, FormChangeBitmapRecordedOnce) { } TEST(PasswordFormMetricsRecorder, FormChangeBitmapRecordedMultipleTimes) { + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); base::HistogramTester histogram_tester; - auto recorder = - CreatePasswordFormMetricsRecorder(true /*is_main_frame_secure*/, nullptr); + auto recorder = CreatePasswordFormMetricsRecorder( + true /*is_main_frame_secure*/, &pref_service); recorder->RecordFormChangeBitmask(PasswordFormMetricsRecorder::kFieldsNumber); recorder->RecordFormChangeBitmask( PasswordFormMetricsRecorder::kFormControlTypes); @@ -685,6 +720,8 @@ void CheckFillingAssistanceTestCase( << ", is_mixed_form: " << std::boolalpha << sub_case.is_mixed_form); base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); base::HistogramTester histogram_tester; FormData form_data = ConvertToFormData(test_case.fields); @@ -706,7 +743,7 @@ void CheckFillingAssistanceTestCase( /*account_store_values=*/{}); auto recorder = CreatePasswordFormMetricsRecorder( - sub_case.is_main_frame_secure, nullptr); + sub_case.is_main_frame_secure, &pref_service); if (test_case.submission_detected) { recorder->CalculateFillingAssistanceMetric( form_data, saved_usernames, saved_passwords, test_case.is_blacklisted, @@ -1091,6 +1128,8 @@ struct FillingSourceTestCase { void CheckFillingSourceTestCase(const FillingSourceTestCase& test_case) { base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); base::HistogramTester histogram_tester; FormData form_data = ConvertToFormData(test_case.fields); @@ -1104,7 +1143,7 @@ void CheckFillingSourceTestCase(const FillingSourceTestCase& test_case) { { auto recorder = CreatePasswordFormMetricsRecorder( - /*is_main_frame_secure=*/true, nullptr); + /*is_main_frame_secure=*/true, &pref_service); recorder->CalculateFillingAssistanceMetric( form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, /*interactions_stats=*/{}, @@ -1201,4 +1240,315 @@ TEST(PasswordFormMetricsRecorder, FillingSourceBothDifferent) { }); } +TEST(PasswordFormMetricsRecorder, StoresUsedForFillingInLast7And28Days) { + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + + std::set<std::pair<base::string16, PasswordForm::Store>> saved_usernames = + ConvertToString16AndStoreSet({"profileuser"}, {"accountuser"}); + std::set<std::pair<base::string16, PasswordForm::Store>> saved_passwords = + ConvertToString16AndStoreSet({"profilepass"}, {"accountpass"}); + + // Phase 1: The user manually enters a credential that's not stored. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "user", .manually_filled = true}, + {.value = "pass", .manually_filled = true, .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + } + + // Phase 2: A credential from the account store is filled. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "accountuser", .automatically_filled = true}, + {.value = "accountpass", + .automatically_filled = true, + .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kFilledFromAccountStore, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromAccountStore, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromAccountStore, 1); + } + + // Phase 3: A credential from the profile store is filled. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "profileuser", .automatically_filled = true}, + {.value = "profilepass", + .automatically_filled = true, + .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kFilledFromProfileStore, 1); + // Even though this credential came from the profile store, a credential + // from the account store was also filled recently (in phase 2). + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromBothStores, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromBothStores, 1); + } + + // Phase 4: The user again manually enters a credential that's not stored. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "user", .manually_filled = true}, + {.value = "pass", .manually_filled = true, .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + // Even though this credential did not come from either store, both stores + // were used recently (in phases 2 and 3). + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromBothStores, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromBothStores, 1); + } +} + +TEST(PasswordFormMetricsRecorder, StoresUsedForFillingInLast7And28DaysExpiry) { + base::test::TaskEnvironment task_environment; + sync_preferences::TestingPrefServiceSyncable pref_service; + PasswordManager::RegisterProfilePrefs(pref_service.registry()); + base::SimpleTestClock clock; + clock.SetNow(base::Time::Now()); + + std::set<std::pair<base::string16, PasswordForm::Store>> saved_usernames = + ConvertToString16AndStoreSet({"profileuser"}, {"accountuser"}); + std::set<std::pair<base::string16, PasswordForm::Store>> saved_passwords = + ConvertToString16AndStoreSet({"profilepass"}, {"accountpass"}); + + // Day 0: A credential from the profile store is filled. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "profileuser", .automatically_filled = true}, + {.value = "profilepass", + .automatically_filled = true, + .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->set_clock_for_testing(&clock); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kFilledFromProfileStore, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromProfileStore, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromProfileStore, 1); + } + + clock.Advance(base::TimeDelta::FromDays(2)); + + // Day 2: A credential from the account store is filled. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "accountuser", .automatically_filled = true}, + {.value = "accountpass", + .automatically_filled = true, + .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->set_clock_for_testing(&clock); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kFilledFromAccountStore, 1); + // Even though this credential came from the account store, a credential + // from the profile store was also filled recently. + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromBothStores, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromBothStores, 1); + } + + clock.Advance(base::TimeDelta::FromDays(6)); + + // Day 8: A credential from the account store is filled (again). + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "accountuser", .automatically_filled = true}, + {.value = "accountpass", + .automatically_filled = true, + .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->set_clock_for_testing(&clock); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kFilledFromAccountStore, 1); + // A credential from the profile store was last filled 8 days ago, so this + // still shows up in the 28-day histogram but not the 7-day one. + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromAccountStore, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromBothStores, 1); + } + + clock.Advance(base::TimeDelta::FromDays(27)); + + // Day 35: The user manually enters a credential that's not stored. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "user", .manually_filled = true}, + {.value = "pass", .manually_filled = true, .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->set_clock_for_testing(&clock); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + // The account store was used 27 days ago, so is still relevant for the + // 28-day histogram. The profile store was last used 35 days ago, so it's + // gone now. + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kFilledFromAccountStore, 1); + } + + clock.Advance(base::TimeDelta::FromDays(2)); + + // Day 37: The user again manually enters a credential that's not stored. + { + base::HistogramTester histogram_tester; + + FormData form_data = ConvertToFormData( + {{.value = "user", .manually_filled = true}, + {.value = "pass", .manually_filled = true, .is_password = true}}); + { + auto recorder = CreatePasswordFormMetricsRecorder( + /*is_main_frame_secure=*/true, &pref_service); + recorder->set_clock_for_testing(&clock); + recorder->CalculateFillingAssistanceMetric( + form_data, saved_usernames, saved_passwords, /*is_blacklisted=*/false, + /*interactions_stats=*/{}, + PasswordAccountStorageUsageLevel::kUsingAccountStorage); + recorder->LogSubmitPassed(); + } + + histogram_tester.ExpectUniqueSample( + "PasswordManager.FillingSource", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + // Now both usages are > 28 days ago. + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast7Days", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.StoresUsedForFillingInLast28Days", + PasswordFormMetricsRecorder::FillingSource::kNotFilled, 1); + } +} + } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_generation_frame_helper.cc b/chromium/components/password_manager/core/browser/password_generation_frame_helper.cc index ae646ee6ff9..057922d8745 100644 --- a/chromium/components/password_manager/core/browser/password_generation_frame_helper.cc +++ b/chromium/components/password_manager/core/browser/password_generation_frame_helper.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/browser/password_generation_frame_helper.h" +#include <memory> + #include "base/optional.h" #include "base/strings/string_util.h" #include "components/autofill/core/browser/field_types.h" @@ -87,8 +89,8 @@ bool PasswordGenerationFrameHelper::IsGenerationEnabled( bool log_debug_data) const { std::unique_ptr<Logger> logger; if (log_debug_data && password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); } GURL url = driver_->GetLastCommittedURL(); diff --git a/chromium/components/password_manager/core/browser/password_generation_frame_helper_unittest.cc b/chromium/components/password_manager/core/browser/password_generation_frame_helper_unittest.cc index 05bdb27afd4..d96a762a26f 100644 --- a/chromium/components/password_manager/core/browser/password_generation_frame_helper_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_generation_frame_helper_unittest.cc @@ -15,6 +15,7 @@ #include "base/test/task_environment.h" #include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/proto/password_requirements.pb.h" #include "components/autofill/core/common/form_data.h" @@ -62,7 +63,6 @@ class TestPasswordManagerDriver : public StubPasswordManagerDriver { ON_CALL(*this, GetLastCommittedURL()) .WillByDefault(testing::ReturnRef(empty_url_)); } - ~TestPasswordManagerDriver() override {} // PasswordManagerDriver implementation. PasswordGenerationFrameHelper* GetPasswordGenerationHelper() override { @@ -140,9 +140,11 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { PasswordRequirementsService* GetPasswordRequirementsService() override { return &password_requirements_service_; } - void SetLastCommittedEntryUrl(const GURL& url) { last_committed_url_ = url; } - const GURL& GetLastCommittedEntryURL() const override { - return last_committed_url_; + void SetLastCommittedEntryUrl(const GURL& url) { + last_committed_origin_ = url::Origin::Create(url); + } + url::Origin GetLastCommittedOrigin() const override { + return last_committed_origin_; } TestPasswordManagerDriver* test_driver() { return &driver_; } @@ -152,7 +154,7 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { scoped_refptr<TestPasswordStore> store_; TestPasswordManagerDriver driver_; PasswordRequirementsService password_requirements_service_; - GURL last_committed_url_; + url::Origin last_committed_origin_; }; } // anonymous namespace @@ -167,7 +169,7 @@ class PasswordGenerationFrameHelperTest : public testing::Test { new TestingPrefServiceSimple()); prefs->registry()->RegisterBooleanPref(prefs::kCredentialsEnableService, true); - client_.reset(new MockPasswordManagerClient(std::move(prefs))); + client_ = std::make_unique<MockPasswordManagerClient>(std::move(prefs)); } void TearDown() override { client_.reset(); } @@ -305,8 +307,10 @@ TEST_F(PasswordGenerationFrameHelperTest, ProcessPasswordRequirements) { std::string response_string; ASSERT_TRUE(response.SerializeToString(&response_string)); - autofill::FormStructure::ParseQueryResponse(response_string, forms, - nullptr); + + autofill::FormStructure::ParseQueryResponse( + response_string, forms, autofill::test::GetEncodedSignatures(forms), + nullptr); GetGenerationHelper()->PrefetchSpec(origin.GetOrigin()); diff --git a/chromium/components/password_manager/core/browser/password_generation_manager.cc b/chromium/components/password_manager/core/browser/password_generation_manager.cc index cf50dbacd26..06509e7815f 100644 --- a/chromium/components/password_manager/core/browser/password_generation_manager.cc +++ b/chromium/components/password_manager/core/browser/password_generation_manager.cc @@ -43,7 +43,7 @@ class PasswordDataForUI : public PasswordFormManagerForUI { PasswordDataForUI& operator=(const PasswordDataForUI&) = delete; // PasswordFormManagerForUI: - const GURL& GetOrigin() const override; + const GURL& GetURL() const override; const std::vector<const PasswordForm*>& GetBestMatches() const override; std::vector<const PasswordForm*> GetFederatedMatches() const override; const PasswordForm& GetPendingCredentials() const override; @@ -53,6 +53,7 @@ class PasswordDataForUI : public PasswordFormManagerForUI { base::span<const CompromisedCredentials> GetCompromisedCredentials() const override; bool IsBlacklisted() const override; + bool WasUnblacklisted() const override; bool IsMovableToAccountStore() const override; void Save() override; void Update(const PasswordForm& credentials_to_update) override; @@ -92,8 +93,8 @@ PasswordDataForUI::PasswordDataForUI( matches_.push_back(&form); } -const GURL& PasswordDataForUI::GetOrigin() const { - return pending_form_.origin; +const GURL& PasswordDataForUI::GetURL() const { + return pending_form_.url; } const std::vector<const PasswordForm*>& PasswordDataForUI::GetBestMatches() @@ -138,6 +139,11 @@ bool PasswordDataForUI::IsBlacklisted() const { return false; } +bool PasswordDataForUI::WasUnblacklisted() const { + // This information should not be relevant hereconst. + return false; +} + bool PasswordDataForUI::IsMovableToAccountStore() const { // This is irrelevant for the generation conflict resolution bubble. return false; diff --git a/chromium/components/password_manager/core/browser/password_generation_manager_unittest.cc b/chromium/components/password_manager/core/browser/password_generation_manager_unittest.cc index d3713c1584f..99ebefc7b06 100644 --- a/chromium/components/password_manager/core/browser/password_generation_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_generation_manager_unittest.cc @@ -38,8 +38,8 @@ constexpr time_t kAnotherTime = 987654321; // Creates a dummy saved credential. PasswordForm CreateSaved() { PasswordForm form; - form.origin = GURL(kURL); - form.signon_realm = form.origin.spec(); + form.url = GURL(kURL); + form.signon_realm = form.url.spec(); form.action = GURL("https://login.example.org"); form.username_value = ASCIIToUTF16("old_username"); form.password_value = ASCIIToUTF16("12345"); @@ -48,7 +48,7 @@ PasswordForm CreateSaved() { PasswordForm CreateSavedFederated() { autofill::PasswordForm federated; - federated.origin = GURL(kURL); + federated.url = GURL(kURL); federated.signon_realm = "federation://example.in/google.com"; federated.type = autofill::PasswordForm::Type::kApi; federated.federation_origin = @@ -60,8 +60,8 @@ PasswordForm CreateSavedFederated() { // Creates a dummy saved PSL credential. PasswordForm CreateSavedPSL() { PasswordForm form; - form.origin = GURL(kSubdomainURL); - form.signon_realm = form.origin.spec(); + form.url = GURL(kSubdomainURL); + form.signon_realm = form.url.spec(); form.action = GURL("https://login.example.org"); form.username_value = ASCIIToUTF16("old_username2"); form.password_value = ASCIIToUTF16("passw0rd"); @@ -72,8 +72,8 @@ PasswordForm CreateSavedPSL() { // Creates a dummy generated password. PasswordForm CreateGenerated() { PasswordForm form; - form.origin = GURL(kURL); - form.signon_realm = form.origin.spec(); + form.url = GURL(kURL); + form.signon_realm = form.url.spec(); form.action = GURL("https://signup.example.org"); form.username_value = ASCIIToUTF16("MyName"); form.password_value = ASCIIToUTF16("Strong password"); @@ -210,7 +210,7 @@ TEST_F(PasswordGenerationManagerTest, GeneratedPasswordAccepted_UpdateUI) { std::unique_ptr<PasswordFormManagerForUI> ui_form = SetUpOverwritingUI(driver.AsWeakPtr()); ASSERT_TRUE(ui_form); - EXPECT_EQ(GURL(kURL), ui_form->GetOrigin()); + EXPECT_EQ(GURL(kURL), ui_form->GetURL()); EXPECT_THAT( ui_form->GetBestMatches(), ElementsAre(Field(&PasswordForm::username_value, ASCIIToUTF16("")))); diff --git a/chromium/components/password_manager/core/browser/password_list_sorter.cc b/chromium/components/password_manager/core/browser/password_list_sorter.cc index a6aeea275ab..9b40dd536eb 100644 --- a/chromium/components/password_manager/core/browser/password_list_sorter.cc +++ b/chromium/components/password_manager/core/browser/password_list_sorter.cc @@ -27,7 +27,8 @@ constexpr char kSortKeyNoFederationSymbol = '-'; } // namespace -std::string CreateSortKey(const autofill::PasswordForm& form) { +std::string CreateSortKey(const autofill::PasswordForm& form, + IgnoreStore ignore_store) { std::string shown_origin; GURL link_url; std::tie(shown_origin, link_url) = GetShownOriginAndLinkUrl(form); @@ -74,7 +75,8 @@ std::string CreateSortKey(const autofill::PasswordForm& form) { // To separate HTTP/HTTPS credentials, add the scheme to the key. key += kSortKeyPartsSeparator + link_url.scheme(); - if (form.in_store == autofill::PasswordForm::Store::kAccountStore) { + if (!ignore_store && + form.in_store == autofill::PasswordForm::Store::kAccountStore) { key += kSortKeyPartsSeparator + std::string("account"); } diff --git a/chromium/components/password_manager/core/browser/password_list_sorter.h b/chromium/components/password_manager/core/browser/password_list_sorter.h index 4130d7b02a6..234875ce587 100644 --- a/chromium/components/password_manager/core/browser/password_list_sorter.h +++ b/chromium/components/password_manager/core/browser/password_list_sorter.h @@ -10,6 +10,8 @@ #include <string> #include <vector> +#include "base/util/type_safety/strong_alias.h" + namespace autofill { struct PasswordForm; } @@ -19,6 +21,7 @@ namespace password_manager { // Multimap from sort key to password forms. using DuplicatesMap = std::multimap<std::string, std::unique_ptr<autofill::PasswordForm>>; +using IgnoreStore = util::StrongAlias<class IgnoreStoreTag, bool>; // Creates key for sorting password or password exception entries. The key is // eTLD+1 followed by the reversed list of domains (e.g. @@ -26,7 +29,10 @@ using DuplicatesMap = // the scheme. If |form| is not blacklisted, username, password and federation // are appended to the key. If not, no further information is added. For Android // credentials the canocial spec is included. -std::string CreateSortKey(const autofill::PasswordForm& form); +// If |ignore_store| is true, forms differing only by the originating password +// store will map to the same key. +std::string CreateSortKey(const autofill::PasswordForm& form, + IgnoreStore ignore_store = IgnoreStore(false)); // Sort entries of |list| based on sort key. The key is the concatenation of // origin, entry type (non-Android credential, Android w/ affiliated web realm diff --git a/chromium/components/password_manager/core/browser/password_list_sorter_unittest.cc b/chromium/components/password_manager/core/browser/password_list_sorter_unittest.cc index 23c28b589cf..79af81964fa 100644 --- a/chromium/components/password_manager/core/browser/password_list_sorter_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_list_sorter_unittest.cc @@ -31,7 +31,7 @@ void SortAndCheckPositions(const std::vector<SortEntry>& test_entries) { for (const SortEntry& entry : test_entries) { auto form = std::make_unique<autofill::PasswordForm>(); form->signon_realm = entry.origin; - form->origin = GURL(entry.origin); + form->url = GURL(entry.origin); form->blacklisted_by_user = entry.is_blacklisted; if (!entry.is_blacklisted) { form->username_value = base::ASCIIToUTF16(entry.username); @@ -56,7 +56,7 @@ void SortAndCheckPositions(const std::vector<SortEntry>& test_entries) { if (entry.expected_position >= 0) { SCOPED_TRACE(testing::Message("position in sorted list: ") << entry.expected_position); - EXPECT_EQ(GURL(entry.origin), list[entry.expected_position]->origin); + EXPECT_EQ(GURL(entry.origin), list[entry.expected_position]->url); if (!entry.is_blacklisted) { EXPECT_EQ(base::ASCIIToUTF16(entry.username), list[entry.expected_position]->username_value); @@ -189,4 +189,18 @@ TEST(PasswordListSorterTest, Sorting_SpecialCharacters) { SortAndCheckPositions(test_cases); } +TEST(PasswordListSorterTest, EntriesDifferingByStoreShouldMapToSameKey) { + autofill::PasswordForm account_form; + account_form.signon_realm = "https://g.com/"; + account_form.url = GURL(account_form.signon_realm); + account_form.blacklisted_by_user = false; + account_form.in_store = autofill::PasswordForm::Store::kAccountStore; + + autofill::PasswordForm profile_form(account_form); + profile_form.in_store = autofill::PasswordForm::Store::kProfileStore; + + EXPECT_EQ(CreateSortKey(account_form, IgnoreStore(true)), + CreateSortKey(profile_form, IgnoreStore(true))); +} + } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_manager.cc b/chromium/components/password_manager/core/browser/password_manager.cc index 4f0463a01a3..176a422e3b2 100644 --- a/chromium/components/password_manager/core/browser/password_manager.cc +++ b/chromium/components/password_manager/core/browser/password_manager.cc @@ -26,7 +26,9 @@ #include "components/autofill/core/common/save_password_progress_logger.h" #include "components/autofill/core/common/signatures.h" #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" +#include "components/password_manager/core/browser/credential_cache.h" #include "components/password_manager/core/browser/field_info_manager.h" +#include "components/password_manager/core/browser/origin_credential_store.h" #include "components/password_manager/core/browser/password_autofill_manager.h" #include "components/password_manager/core/browser/password_form_manager.h" #include "components/password_manager/core/browser/password_generation_frame_helper.h" @@ -48,7 +50,9 @@ #endif using autofill::ACCOUNT_CREATION_PASSWORD; +using autofill::FieldRendererId; using autofill::FormData; +using autofill::FormRendererId; using autofill::FormStructure; using autofill::NEW_PASSWORD; using autofill::NOT_USERNAME; @@ -58,6 +62,8 @@ using autofill::UNKNOWN_TYPE; using autofill::USERNAME; using autofill::mojom::PasswordFormFieldPredictionType; using base::NumberToString; +using BlacklistedStatus = + password_manager::OriginCredentialStore::BlacklistedStatus; #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) using password_manager::metrics_util::GaiaPasswordHashChange; #endif // SYNC_PASSWORD_REUSE_DETECTION_ENABLED @@ -223,6 +229,11 @@ void PasswordManager::RegisterProfilePrefs( registry->RegisterDictionaryPref(prefs::kAccountStoragePerAccountSettings); + registry->RegisterTimePref(prefs::kProfileStoreDateLastUsedForFilling, + base::Time()); + registry->RegisterTimePref(prefs::kAccountStoreDateLastUsedForFilling, + base::Time()); + #if defined(OS_MACOSX) registry->RegisterIntegerPref(prefs::kKeychainMigrationStatus, 4 /* MIGRATED_DELETED */); @@ -293,7 +304,7 @@ void PasswordManager::OnPasswordNoLongerGenerated(PasswordManagerDriver* driver, void PasswordManager::SetGenerationElementAndReasonForForm( password_manager::PasswordManagerDriver* driver, const FormData& form_data, - const base::string16& generation_element, + FieldRendererId generation_element, bool is_manually_triggered) { DCHECK(client_->IsSavingAndFillingEnabled(form_data.url)); @@ -304,11 +315,34 @@ void PasswordManager::SetGenerationElementAndReasonForForm( } } +void PasswordManager::MarkWasUnblacklistedInFormManagers( + CredentialCache* credential_cache) { + if (owned_submitted_form_manager_) { + const OriginCredentialStore& credential_store = + credential_cache->GetCredentialStore( + url::Origin::Create(owned_submitted_form_manager_->GetURL())); + if (credential_store.GetBlacklistedStatus() == + BlacklistedStatus::kWasBlacklisted) { + owned_submitted_form_manager_->MarkWasUnblacklisted(); + } + } + + for (const auto& form_manager : form_managers_) { + const OriginCredentialStore& credential_store = + credential_cache->GetCredentialStore( + url::Origin::Create(form_manager->GetURL())); + if (credential_store.GetBlacklistedStatus() == + BlacklistedStatus::kWasBlacklisted) { + form_manager->MarkWasUnblacklisted(); + } + } +} + void PasswordManager::DidNavigateMainFrame(bool form_may_be_submitted) { std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogBoolean(Logger::STRING_DID_NAVIGATE_MAIN_FRAME, form_may_be_submitted); } @@ -483,7 +517,7 @@ void PasswordManager::OnPasswordFormsParsed( driver ? driver->GetPasswordGenerationHelper() : nullptr; if (password_generation_manager) { password_generation_manager->PrefetchSpec( - client_->GetLastCommittedEntryURL().GetOrigin()); + client_->GetLastCommittedOrigin().GetURL()); } } @@ -492,8 +526,8 @@ void PasswordManager::CreatePendingLoginManagers( const std::vector<FormData>& forms_data) { std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogMessage(Logger::STRING_CREATE_LOGIN_MANAGERS_METHOD); } @@ -574,8 +608,8 @@ PasswordFormManager* PasswordManager::ProvisionallySaveForm( bool is_manual_fallback) { std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogMessage(Logger::STRING_PROVISIONALLY_SAVE_FORM_METHOD); } if (!client_->IsSavingAndFillingEnabled(submitted_form.url)) { @@ -588,11 +622,11 @@ PasswordFormManager* PasswordManager::ProvisionallySaveForm( if (store_password_called_) return nullptr; - const GURL& origin = submitted_form.url; - if (ShouldBlockPasswordForSameOriginButDifferentScheme(origin)) { + const GURL& submitted_url = submitted_form.url; + if (ShouldBlockPasswordForSameOriginButDifferentScheme(submitted_url)) { RecordProvisionalSaveFailure( - PasswordManagerMetricsRecorder::SAVING_ON_HTTP_AFTER_HTTPS, origin, - logger.get()); + PasswordManagerMetricsRecorder::SAVING_ON_HTTP_AFTER_HTTPS, + submitted_url, logger.get()); return nullptr; } @@ -634,10 +668,9 @@ PasswordFormManager* PasswordManager::ProvisionallySaveForm( manager->set_not_submitted(); } - // Cache the user-visible URL (i.e., the one seen in the omnibox). Once the - // post-submit navigation concludes, we compare the landing URL against the - // cached and report the difference through UMA. - main_frame_url_ = client_->GetMainFrameURL(); + // Cache the committed URL. Once the post-submit navigation concludes, we + // compare the landing URL against the cached and report the difference. + submitted_form_url_ = submitted_url; ReportSubmittedFormFrameMetric(driver, *matched_manager->GetSubmittedForm()); @@ -667,7 +700,7 @@ void PasswordManager::PresaveGeneratedPassword( PasswordManagerDriver* driver, const FormData& form, const base::string16& generated_password, - const base::string16& generation_element) { + FieldRendererId generation_element) { PasswordFormManager* form_manager = GetMatchedManager(driver, form); UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager", !form_manager); @@ -681,12 +714,11 @@ void PasswordManager::PresaveGeneratedPassword( void PasswordManager::UpdateStateOnUserInput( PasswordManagerDriver* driver, - const base::string16& form_identifier, - const base::string16& field_identifier, + FormRendererId form_id, + FieldRendererId field_id, const base::string16& field_value) { for (std::unique_ptr<PasswordFormManager>& manager : form_managers_) { - if (manager->UpdateStateOnUserInput(form_identifier, field_identifier, - field_value)) { + if (manager->UpdateStateOnUserInput(form_id, field_id, field_value)) { ProvisionallySaveForm(manager->observed_form(), driver, true); if (manager->is_submitted() && !manager->HasGeneratedPassword()) { ShowManualFallbackForSavingImpl(manager.get(), @@ -704,13 +736,34 @@ void PasswordManager::OnPasswordNoLongerGenerated( for (std::unique_ptr<PasswordFormManager>& manager : form_managers_) manager->PasswordNoLongerGenerated(); } + +void PasswordManager::OnPasswordFormRemoved(PasswordManagerDriver* driver, + FormRendererId form_id) { + for (auto& manager : form_managers_) { + if (driver && !manager->GetDriver()) + manager->SetDriver(driver->AsWeakPtr()); + if (manager->DoesManageAccordingToRendererId(form_id, driver)) { + if (manager->is_submitted()) + OnLoginSuccessful(); + return; + } + } +} + +void PasswordManager::OnIframeDetach(const std::string& frame_id) { + PasswordFormManager* submitted_manager = GetSubmittedManager(); + if (submitted_manager && + submitted_manager->observed_form().frame_id == frame_id) { + OnLoginSuccessful(); + } +} #endif bool PasswordManager::IsAutomaticSavePromptAvailable() { std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogMessage(Logger::STRING_CAN_PROVISIONAL_MANAGER_SAVE_METHOD); } @@ -729,7 +782,7 @@ bool PasswordManager::IsAutomaticSavePromptAvailable() { // We just give up. RecordProvisionalSaveFailure( PasswordManagerMetricsRecorder::MATCHING_NOT_COMPLETE, - submitted_manager->GetOrigin(), logger.get()); + submitted_manager->GetURL(), logger.get()); return false; } @@ -737,10 +790,10 @@ bool PasswordManager::IsAutomaticSavePromptAvailable() { } bool PasswordManager::ShouldBlockPasswordForSameOriginButDifferentScheme( - const GURL& origin) const { - const GURL& old_origin = main_frame_url_.GetOrigin(); - return old_origin.host_piece() == origin.host_piece() && - old_origin.SchemeIsCryptographic() && !origin.SchemeIsCryptographic(); + const GURL& url) const { + return submitted_form_url_.host_piece() == url.host_piece() && + submitted_form_url_.SchemeIsCryptographic() && + !url.SchemeIsCryptographic(); } void PasswordManager::OnPasswordFormsRendered( @@ -750,8 +803,8 @@ void PasswordManager::OnPasswordFormsRendered( CreatePendingLoginManagers(driver, visible_forms_data); std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogMessage(Logger::STRING_ON_PASSWORD_FORMS_RENDERED_METHOD); } @@ -833,16 +886,14 @@ void PasswordManager::OnPasswordFormsRendered( void PasswordManager::OnLoginSuccessful() { if (autofill_assistant_mode_ == AutofillAssistantMode::kRunning) { - // Autofillassistan performs one login. Only one prompt should - // be suppressed. - SetAutofillAssistantMode(AutofillAssistantMode::kNotRunning); + // Suppress prompts while Autofill Assistant is running. return; } std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); logger->LogMessage(Logger::STRING_ON_ASK_USER_OR_SAVE_PASSWORD); } @@ -874,7 +925,7 @@ void PasswordManager::OnLoginSuccessful() { *submitted_manager->GetSubmittedForm())) { RecordProvisionalSaveFailure( PasswordManagerMetricsRecorder::SYNC_CREDENTIAL, - submitted_manager->GetOrigin(), logger.get()); + submitted_manager->GetURL(), logger.get()); owned_submitted_form_manager_.reset(); return; } @@ -883,7 +934,7 @@ void PasswordManager::OnLoginSuccessful() { UMA_HISTOGRAM_BOOLEAN( "PasswordManager.SuccessfulLoginHappened", - submitted_manager->GetSubmittedForm()->origin.SchemeIsCryptographic()); + submitted_manager->GetSubmittedForm()->url.SchemeIsCryptographic()); // If the form is eligible only for saving fallback, it shouldn't go here. DCHECK(!submitted_manager->GetPendingCredentials().only_for_fallback); @@ -991,8 +1042,8 @@ void PasswordManager::ProcessAutofillPredictions( const std::vector<FormStructure*>& forms) { std::unique_ptr<BrowserSavePasswordProgressLogger> logger; if (password_manager_util::IsLoggingActive(client_)) { - logger.reset( - new BrowserSavePasswordProgressLogger(client_->GetLogManager())); + logger = std::make_unique<BrowserSavePasswordProgressLogger>( + client_->GetLogManager()); } for (const FormStructure* form : forms) { @@ -1071,7 +1122,7 @@ void PasswordManager::RecordProvisionalSaveFailure( BrowserSavePasswordProgressLogger* logger) { if (client_ && client_->GetMetricsRecorder()) { client_->GetMetricsRecorder()->RecordProvisionalSaveFailure( - failure, main_frame_url_, form_origin, logger); + failure, submitted_form_url_, form_origin, logger); } } @@ -1101,14 +1152,12 @@ void PasswordManager::ReportSubmittedFormFrameMetric( metrics_util::SubmittedFormFrame frame; if (driver->IsMainFrame()) { frame = metrics_util::SubmittedFormFrame::MAIN_FRAME; - } else if (form.origin == main_frame_url_) { + } else if (form.url == client()->GetLastCommittedURL()) { frame = metrics_util::SubmittedFormFrame::IFRAME_WITH_SAME_URL_AS_MAIN_FRAME; } else { - GURL::Replacements rep; - rep.SetPathStr(""); std::string main_frame_signon_realm = - main_frame_url_.ReplaceComponents(rep).spec(); + GetSignonRealm(client()->GetLastCommittedURL()); frame = (main_frame_signon_realm == form.signon_realm) ? metrics_util::SubmittedFormFrame:: IFRAME_WITH_DIFFERENT_URL_SAME_SIGNON_REALM_AS_MAIN_FRAME @@ -1178,6 +1227,10 @@ void PasswordManager::SetAutofillAssistantMode(AutofillAssistantMode mode) { } } +AutofillAssistantMode PasswordManager::GetAutofillAssistantMode() const { + return autofill_assistant_mode_; +} + void PasswordManager::ResetAutofillAssistantMode() { // The timeout is 0 only in the dedicated test. Otherwise, the call can happen // only due to a bug. diff --git a/chromium/components/password_manager/core/browser/password_manager.h b/chromium/components/password_manager/core/browser/password_manager.h index ef4ddc0f4d0..7087751b236 100644 --- a/chromium/components/password_manager/core/browser/password_manager.h +++ b/chromium/components/password_manager/core/browser/password_manager.h @@ -20,6 +20,7 @@ #include "components/autofill/core/common/password_form_fill_data.h" #include "components/autofill/core/common/renderer_id.h" #include "components/autofill/core/common/signatures.h" +#include "components/password_manager/core/browser/credential_cache.h" #include "components/password_manager/core/browser/form_parsing/password_field_prediction.h" #include "components/password_manager/core/browser/form_submission_observer.h" #include "components/password_manager/core/browser/leak_detection/leak_detection_check_factory.h" @@ -105,9 +106,14 @@ class PasswordManager : public FormSubmissionObserver { void SetGenerationElementAndReasonForForm( PasswordManagerDriver* driver, const autofill::FormData& form_data, - const base::string16& generation_element, + autofill::FieldRendererId generation_element, bool is_manually_triggered); + // Called upon navigation to persist the state from |CredentialCache| + // used to decide when to record + // |PasswordManager.ResultOfSavingFlowAfterUnblacklistin|. + void MarkWasUnblacklistedInFormManagers(CredentialCache* credential_cache); + // FormSubmissionObserver: void DidNavigateMainFrame(bool form_may_be_submitted) override; @@ -208,8 +214,13 @@ class PasswordManager : public FormSubmissionObserver { // Notifies that Credential Management API function store() is called. void NotifyStorePasswordCalled(); + // Sets the autofill-assistant mode. Certain prompts will be disabled while + // autofill-assistant is running. See |AutofillAssistantMode|. void SetAutofillAssistantMode(AutofillAssistantMode mode); + // Returns the currently set autofill-assistant mode. + AutofillAssistantMode GetAutofillAssistantMode() const; + #if defined(OS_IOS) // TODO(https://crbug.com/866444): Use these methods instead olds ones when // the old parser is gone. @@ -221,20 +232,26 @@ class PasswordManager : public FormSubmissionObserver { void PresaveGeneratedPassword(PasswordManagerDriver* driver, const autofill::FormData& form, const base::string16& generated_password, - const base::string16& generation_element); + autofill::FieldRendererId generation_element); // Updates the state if the PasswordFormManager which corresponds to the form // with |form_identifier|. In case if there is a presaved credential it // updates the presaved credential. void UpdateStateOnUserInput(PasswordManagerDriver* driver, - const base::string16& form_identifier, - const base::string16& field_identifier, + autofill::FormRendererId form_id, + autofill::FieldRendererId field_id, const base::string16& field_value); // Stops treating a password as generated. |driver| corresponds to the // form parent frame. void OnPasswordNoLongerGenerated(PasswordManagerDriver* driver); + void OnPasswordFormRemoved(PasswordManagerDriver* driver, + autofill::FormRendererId form_id); + + // Checks if there is a submitted PasswordFormManager for a form from the + // detached frame. + void OnIframeDetach(const std::string& frame_id); #endif private: @@ -378,8 +395,8 @@ class PasswordManager : public FormSubmissionObserver { // Server predictions for the forms on the page. std::map<autofill::FormSignature, FormPredictions> predictions_; - // The user-visible URL from the last time a password was provisionally saved. - GURL main_frame_url_; + // The URL of the last submitted form. + GURL submitted_form_url_; // True if Credential Management API function store() was called. In this case // PasswordManager does not need to show a save/update prompt since diff --git a/chromium/components/password_manager/core/browser/password_manager_client.cc b/chromium/components/password_manager/core/browser/password_manager_client.cc index df7ebde0bcd..ac7fd054e2c 100644 --- a/chromium/components/password_manager/core/browser/password_manager_client.cc +++ b/chromium/components/password_manager/core/browser/password_manager_client.cc @@ -25,7 +25,7 @@ bool PasswordManagerClient::IsFillingFallbackEnabled(const GURL& url) const { return true; } -void PasswordManagerClient::PostHSTSQueryForHost(const GURL& origin, +void PasswordManagerClient::PostHSTSQueryForHost(const url::Origin& origin, HSTSCallback callback) const { std::move(callback).Run(HSTSResult::kError); } @@ -39,13 +39,13 @@ BiometricAuthenticator* PasswordManagerClient::GetBiometricAuthenticator() { void PasswordManagerClient::GeneratePassword() {} void PasswordManagerClient::UpdateCredentialCache( - const GURL& origin, + const url::Origin& origin, const std::vector<const autofill::PasswordForm*>& best_matches, bool is_blacklisted) {} void PasswordManagerClient::PasswordWasAutofilled( const std::vector<const autofill::PasswordForm*>& best_matches, - const GURL& origin, + const url::Origin& origin, const std::vector<const autofill::PasswordForm*>* federated_matches) {} void PasswordManagerClient::AutofillHttpAuth( @@ -58,6 +58,7 @@ void PasswordManagerClient::NotifyUserCredentialsWereLeaked( const base::string16& username) {} void PasswordManagerClient::TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint access_point, base::OnceCallback<void(ReauthSucceeded)> reauth_callback) { std::move(reauth_callback).Run(ReauthSucceeded(false)); } @@ -115,11 +116,7 @@ PasswordManagerClient::GetAutofillDownloadManager() { return nullptr; } -const GURL& PasswordManagerClient::GetMainFrameURL() const { - return GURL::EmptyGURL(); -} - -bool PasswordManagerClient::IsMainFrameSecure() const { +bool PasswordManagerClient::IsCommittedMainFrameSecure() const { return false; } @@ -148,4 +145,8 @@ bool PasswordManagerClient::IsUnderAdvancedProtection() const { return false; } +AutofillAssistantMode PasswordManagerClient::GetAutofillAssistantMode() const { + return GetPasswordManager()->GetAutofillAssistantMode(); +} + } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_manager_client.h b/chromium/components/password_manager/core/browser/password_manager_client.h index b1589f91539..7892425a1d4 100644 --- a/chromium/components/password_manager/core/browser/password_manager_client.h +++ b/chromium/components/password_manager/core/browser/password_manager_client.h @@ -21,6 +21,7 @@ #include "components/password_manager/core/browser/http_auth_manager.h" #include "components/password_manager/core/browser/leak_detection_dialog_utils.h" #include "components/password_manager/core/browser/manage_passwords_referrer.h" +#include "components/password_manager/core/browser/password_manager.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_reuse_detector.h" #include "components/password_manager/core/browser/password_store.h" @@ -49,6 +50,11 @@ class IdentityManager; namespace signin_metrics { enum class AccessPoint; +enum class ReauthAccessPoint; +} // namespace signin_metrics + +namespace url { +class Origin; } class GURL; @@ -65,7 +71,6 @@ class FieldInfoManager; class PasswordFeatureManager; class BiometricAuthenticator; class PasswordFormManagerForUI; -class PasswordManager; class PasswordManagerDriver; class PasswordManagerMetricsRecorder; class HttpAuthManager; @@ -114,7 +119,7 @@ class PasswordManagerClient { // Checks asynchronously whether HTTP Strict Transport Security (HSTS) is // active for the host of the given origin. Notifies |callback| with the // result on the calling thread. - virtual void PostHSTSQueryForHost(const GURL& origin, + virtual void PostHSTSQueryForHost(const url::Origin& origin, HSTSCallback callback) const; // Informs the embedder of a password form that can be saved or updated in @@ -171,7 +176,7 @@ class PasswordManagerClient { // |callback| should be invoked with the chosen form. virtual bool PromptUserToChooseCredentials( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin, + const url::Origin& origin, const CredentialsCallback& callback) = 0; // Instructs the client to show the Touch To Fill UI. @@ -191,7 +196,7 @@ class PasswordManagerClient { // auto signed in to. virtual void NotifyUserAutoSignin( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin) = 0; + const url::Origin& origin) = 0; // Inform the embedder that automatic signin would have happened if the user // had been through the first-run experience to ensure their opt-in. |form| @@ -211,7 +216,7 @@ class PasswordManagerClient { // Update the CredentialCache used to display fetched credentials in the UI. // Currently only implemented on Android. virtual void UpdateCredentialCache( - const GURL& origin, + const url::Origin& origin, const std::vector<const autofill::PasswordForm*>& best_matches, bool is_blacklisted); @@ -229,7 +234,7 @@ class PasswordManagerClient { // implementation is a noop. virtual void PasswordWasAutofilled( const std::vector<const autofill::PasswordForm*>& best_matches, - const GURL& origin, + const url::Origin& origin, const std::vector<const autofill::PasswordForm*>* federated_matches); // Sends username/password from |preferred_match| for filling in the http auth @@ -242,9 +247,12 @@ class PasswordManagerClient { const GURL& origin, const base::string16& username); - // Requests a reauth for the primary account and triggers the - // |reauth_callback| with ReauthSucceeded(true) if reauthentication succeeded. + // Requests a reauth for the primary account with |access_point| representing + // where the reauth was triggered. + // Triggers the |reauth_callback| with ReauthSucceeded(true) if + // reauthentication succeeded. virtual void TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint access_point, base::OnceCallback<void(ReauthSucceeded)> reauth_callback); // Redirects the user to a sign-in in a new tab. |access_point| is used for @@ -298,13 +306,14 @@ class PasswordManagerClient { // Returns the AutofillDownloadManager for votes uploading. virtual autofill::AutofillDownloadManager* GetAutofillDownloadManager(); - // Returns the main frame URL. - virtual const GURL& GetMainFrameURL() const; - // Returns true if the main frame URL has a secure origin. - virtual bool IsMainFrameSecure() const; + virtual bool IsCommittedMainFrameSecure() const; - virtual const GURL& GetLastCommittedEntryURL() const = 0; + // Returns the committed main frame URL. + virtual const GURL& GetLastCommittedURL() const = 0; + + // Returns last committed origin of the main frame. + virtual url::Origin GetLastCommittedOrigin() const = 0; // Use this to filter credentials before handling them in password manager. virtual const CredentialsFilter* GetStoreResultFilter() const = 0; @@ -402,6 +411,9 @@ class PasswordManagerClient { // Returns a FieldInfoManager associated with the current profile. virtual FieldInfoManager* GetFieldInfoManager() const = 0; + // Returns the currently set autofill-assistant mode. + virtual AutofillAssistantMode GetAutofillAssistantMode() const; + private: DISALLOW_COPY_AND_ASSIGN(PasswordManagerClient); }; diff --git a/chromium/components/password_manager/core/browser/password_manager_client_helper.cc b/chromium/components/password_manager/core/browser/password_manager_client_helper.cc index 4fc18c3c45a..c6ff2da28ae 100644 --- a/chromium/components/password_manager/core/browser/password_manager_client_helper.cc +++ b/chromium/components/password_manager/core/browser/password_manager_client_helper.cc @@ -5,6 +5,7 @@ #include "components/password_manager/core/browser/password_manager_client_helper.h" #include "components/password_manager/core/browser/password_bubble_experiment.h" +#include "components/password_manager/core/browser/password_feature_manager.h" #include "components/password_manager/core/browser/password_form_manager_for_ui.h" #include "components/password_manager/core/browser/password_manager.h" #include "components/password_manager/core/common/password_manager_features.h" @@ -19,7 +20,7 @@ PasswordManagerClientHelper::PasswordManagerClientHelper( DCHECK(delegate_); } -PasswordManagerClientHelper::~PasswordManagerClientHelper() {} +PasswordManagerClientHelper::~PasswordManagerClientHelper() = default; void PasswordManagerClientHelper::NotifyUserCouldBeAutoSignedIn( std::unique_ptr<autofill::PasswordForm> form) { @@ -33,16 +34,14 @@ void PasswordManagerClientHelper::NotifySuccessfulLoginWithExistingPassword( if (!possible_auto_sign_in_ || possible_auto_sign_in_->username_value != form.username_value || possible_auto_sign_in_->password_value != form.password_value || - possible_auto_sign_in_->origin != form.origin || + possible_auto_sign_in_->url != form.url || !ShouldPromptToEnableAutoSignIn()) { possible_auto_sign_in_.reset(); } // Check if it is necessary to prompt user to enable auto sign-in. if (possible_auto_sign_in_) { delegate_->PromptUserToEnableAutosignin(); - } else if (base::FeatureList::IsEnabled( - password_manager::features::kEnablePasswordsAccountStorage) && - submitted_manager->IsMovableToAccountStore()) { + } else if (ShouldPromptToMovePasswordToAccount(*submitted_manager)) { delegate_->PromptUserToMovePasswordToAccount(std::move(submitted_manager)); } } @@ -84,4 +83,14 @@ bool PasswordManagerClientHelper::ShouldPromptToEnableAutoSignIn() const { !delegate_->IsIncognito(); } +bool PasswordManagerClientHelper::ShouldPromptToMovePasswordToAccount( + const PasswordFormManagerForUI& submitted_manager) const { + return delegate_->GetPasswordFeatureManager() + ->ShouldShowAccountStorageBubbleUi() && + delegate_->GetPasswordFeatureManager()->GetDefaultPasswordStore() == + autofill::PasswordForm::Store::kAccountStore && + submitted_manager.IsMovableToAccountStore() && + !delegate_->IsIncognito(); +} + } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_manager_client_helper.h b/chromium/components/password_manager/core/browser/password_manager_client_helper.h index 387288c4f85..ff588801b97 100644 --- a/chromium/components/password_manager/core/browser/password_manager_client_helper.h +++ b/chromium/components/password_manager/core/browser/password_manager_client_helper.h @@ -49,6 +49,12 @@ class PasswordManagerClientHelper { // is the case for first run experience, and only for non-incognito mode. bool ShouldPromptToEnableAutoSignIn() const; + // Returns whether the user should be prompted to move the submitted password + // to the account-scoped store. This is the case if the password is movable, + // the corresponding feature flag is enabled, and only for non-incognito mode. + bool ShouldPromptToMovePasswordToAccount( + const PasswordFormManagerForUI& submitted_manager) const; + PasswordManagerClient* delegate_; // Set during 'NotifyUserCouldBeAutoSignedIn' in order to store the diff --git a/chromium/components/password_manager/core/browser/password_manager_client_helper_unittest.cc b/chromium/components/password_manager/core/browser/password_manager_client_helper_unittest.cc index a4269bb1fbd..affef187b88 100644 --- a/chromium/components/password_manager/core/browser/password_manager_client_helper_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_manager_client_helper_unittest.cc @@ -54,7 +54,7 @@ PasswordForm CreateForm(std::string username, PasswordForm form; form.username_value = base::ASCIIToUTF16(username); form.password_value = base::ASCIIToUTF16(password); - form.origin = origin; + form.url = origin; form.signon_realm = origin.spec(); return form; } @@ -104,10 +104,13 @@ TEST_F(PasswordManagerClientHelperTest, PromptAutosigninAfterSuccessfulLogin) { CreateFormManager(&form, /*is_movable=*/true)); } -TEST_F(PasswordManagerClientHelperTest, PromptAutosigninDisabledInIncognito) { +TEST_F(PasswordManagerClientHelperTest, + PromptAutosigninAndMoveDisabledInIncognito) { EXPECT_CALL(*client(), IsIncognito) .Times(AnyNumber()) .WillRepeatedly(Return(true)); + // In Incognito, both the auto-signin and the "Move password to account?" + // bubbles should be disabled. EXPECT_CALL(*client(), PromptUserToEnableAutosignin).Times(0); EXPECT_CALL(*client(), PromptUserToMovePasswordToAccount).Times(0); @@ -118,14 +121,33 @@ TEST_F(PasswordManagerClientHelperTest, PromptAutosigninDisabledInIncognito) { CreateFormManager(&form, /*is_movable=*/true)); } -TEST_F(PasswordManagerClientHelperTest, PromptMoveForMovableForm) { - base::test::ScopedFeatureList account_storage_feature; - account_storage_feature.InitAndEnableFeature( - password_manager::features::kEnablePasswordsAccountStorage); +TEST_F(PasswordManagerClientHelperTest, PromptMoveForMovableFormInAccountMode) { + EXPECT_CALL(*client()->GetPasswordFeatureManager(), + ShouldShowAccountStorageBubbleUi) + .WillOnce(Return(true)); + EXPECT_CALL(*client()->GetPasswordFeatureManager(), GetDefaultPasswordStore) + .WillOnce(Return(autofill::PasswordForm::Store::kAccountStore)); EXPECT_CALL(*client(), PromptUserToMovePasswordToAccount); EXPECT_CALL(*client(), PromptUserToEnableAutosignin).Times(0); - // Indicate successful login without matching form. + // Indicate successful login. + const PasswordForm form = + CreateForm(kTestUsername, kTestPassword, GURL(kTestOrigin)); + helper()->NotifySuccessfulLoginWithExistingPassword( + CreateFormManager(&form, /*is_movable=*/true)); +} + +TEST_F(PasswordManagerClientHelperTest, + NoPromptToMoveForMovableFormInProfileMode) { + EXPECT_CALL(*client()->GetPasswordFeatureManager(), + ShouldShowAccountStorageBubbleUi) + .WillOnce(Return(true)); + EXPECT_CALL(*client()->GetPasswordFeatureManager(), GetDefaultPasswordStore) + .WillOnce(Return(autofill::PasswordForm::Store::kProfileStore)); + EXPECT_CALL(*client(), PromptUserToMovePasswordToAccount).Times(0); + EXPECT_CALL(*client(), PromptUserToEnableAutosignin).Times(0); + + // Indicate successful login. const PasswordForm form = CreateForm(kTestUsername, kTestPassword, GURL(kTestOrigin)); helper()->NotifySuccessfulLoginWithExistingPassword( diff --git a/chromium/components/password_manager/core/browser/password_manager_driver.h b/chromium/components/password_manager/core/browser/password_manager_driver.h index 48cdd98f121..1250f1be3f3 100644 --- a/chromium/components/password_manager/core/browser/password_manager_driver.h +++ b/chromium/components/password_manager/core/browser/password_manager_driver.h @@ -48,8 +48,13 @@ class PasswordManagerDriver // Informs the driver that there are no saved credentials in the password // store for the current page. + // |should_show_popup_without_passwords| instructs the driver that the popup + // should be shown even without password suggestions. This is set to true if + // the popup will include another item that the driver doesn't know about + // (e.g. a promo to unlock passwords from the user's Google Account). // TODO(https://crbug.com/621355): Remove and observe FormFetcher instead. - virtual void InformNoSavedCredentials() {} + virtual void InformNoSavedCredentials( + bool should_show_popup_without_passwords) {} // Notifies the driver that a password can be generated on the fields // identified by |form|. diff --git a/chromium/components/password_manager/core/browser/password_manager_features_util.cc b/chromium/components/password_manager/core/browser/password_manager_features_util.cc index 7bd4931784d..a287f60d5a8 100644 --- a/chromium/components/password_manager/core/browser/password_manager_features_util.cc +++ b/chromium/components/password_manager/core/browser/password_manager_features_util.cc @@ -4,6 +4,9 @@ #include "components/password_manager/core/browser/password_manager_features_util.h" +#include <algorithm> + +#include "base/containers/flat_set.h" #include "base/metrics/histogram_functions.h" #include "base/values.h" #include "components/autofill/core/common/gaia_id_hash.h" @@ -71,6 +74,20 @@ PasswordForm::Store PasswordStoreFromInt(int value) { const char kAccountStorageOptedInKey[] = "opted_in"; const char kAccountStorageDefaultStoreKey[] = "default_store"; +// Returns the total number of accounts for which an opt-in to the account +// storage exists. Used for metrics. +int GetNumberOfOptedInAccounts(const PrefService* pref_service) { + const base::DictionaryValue* global_pref = + pref_service->GetDictionary(prefs::kAccountStoragePerAccountSettings); + int count = 0; + for (const std::pair<std::string, std::unique_ptr<base::Value>>& entry : + *global_pref) { + if (entry.second->FindBoolKey(kAccountStorageOptedInKey).value_or(false)) + ++count; + } + return count; +} + // Helper class for reading account storage settings for a given account. class AccountStorageSettingsReader { public: @@ -221,6 +238,11 @@ void OptInToAccountStorage(PrefService* pref_service, ScopedAccountStorageSettingsUpdate(pref_service, GaiaIdHash::FromGaiaId(gaia_id)) .SetOptedIn(); + + // Record the total number of (now) opted-in accounts. + base::UmaHistogramExactLinear( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn", + GetNumberOfOptedInAccounts(pref_service), 10); } void OptOutOfAccountStorageAndClearSettings( @@ -241,13 +263,25 @@ void OptOutOfAccountStorageAndClearSettings( // opt-out UI was triggered. return; } + + OptOutOfAccountStorageAndClearSettingsForAccount(pref_service, gaia_id); +} + +void OptOutOfAccountStorageAndClearSettingsForAccount( + PrefService* pref_service, + const std::string& gaia_id) { ScopedAccountStorageSettingsUpdate(pref_service, GaiaIdHash::FromGaiaId(gaia_id)) .ClearAllSettings(); + + // Record the total number of (still) opted-in accounts. + base::UmaHistogramExactLinear( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptOut", + GetNumberOfOptedInAccounts(pref_service), 10); } -bool ShouldShowPasswordStorePicker(const PrefService* pref_service, - const syncer::SyncService* sync_service) { +bool ShouldShowAccountStorageBubbleUi(const PrefService* pref_service, + const syncer::SyncService* sync_service) { return !sync_service->IsSyncFeatureEnabled() && (IsOptedInForAccountStorage(pref_service, sync_service) || IsUserEligibleForAccountStorage(sync_service)); @@ -291,13 +325,48 @@ void SetDefaultPasswordStore(PrefService* pref_service, // but is ultimately harmless - just do nothing here. return; } + ScopedAccountStorageSettingsUpdate(pref_service, GaiaIdHash::FromGaiaId(gaia_id)) .SetDefaultStore(default_store); + + base::UmaHistogramEnumeration("PasswordManager.DefaultPasswordStoreSet", + default_store); +} + +void KeepAccountStorageSettingsOnlyForUsers( + PrefService* pref_service, + const std::vector<std::string>& gaia_ids) { + DCHECK(pref_service); + + // Build a set of hashes of all the Gaia IDs. + std::vector<std::string> hashes_to_keep_list; + for (const std::string& gaia_id : gaia_ids) + hashes_to_keep_list.push_back(GaiaIdHash::FromGaiaId(gaia_id).ToBase64()); + base::flat_set<std::string> hashes_to_keep(std::move(hashes_to_keep_list)); + + // Now remove any settings for account that are *not* in the set of hashes. + // DictionaryValue doesn't allow removing elements while iterating, so first + // collect all the keys to remove, then actually remove them in a second pass. + DictionaryPrefUpdate update(pref_service, + prefs::kAccountStoragePerAccountSettings); + std::vector<std::string> keys_to_remove; + for (auto kv : update->DictItems()) { + if (!hashes_to_keep.contains(kv.first)) + keys_to_remove.push_back(kv.first); + } + for (const std::string& key_to_remove : keys_to_remove) + update->RemoveKey(key_to_remove); } void ClearAccountStorageSettingsForAllUsers(PrefService* pref_service) { DCHECK(pref_service); + + // Record the total number of opted-in accounts before clearing them. + base::UmaHistogramExactLinear( + "PasswordManager.AccountStorage.ClearedOptInForAllAccounts", + GetNumberOfOptedInAccounts(pref_service), 10); + pref_service->ClearPref(prefs::kAccountStoragePerAccountSettings); } diff --git a/chromium/components/password_manager/core/browser/password_manager_features_util.h b/chromium/components/password_manager/core/browser/password_manager_features_util.h index a524b4c391a..4f46739870c 100644 --- a/chromium/components/password_manager/core/browser/password_manager_features_util.h +++ b/chromium/components/password_manager/core/browser/password_manager_features_util.h @@ -61,16 +61,21 @@ void OptOutOfAccountStorageAndClearSettings( PrefService* pref_service, const syncer::SyncService* sync_service); -// Whether it makes sense to ask the user about the store when saving a -// password (i.e. profile or account store). This is true if the user has -// opted in already, or hasn't opted in but all other requirements are met (i.e. -// there is a signed-in user, Sync-the-feature is not enabled, etc). -// |pref_service| must not be null. -// |sync_service| may be null (commonly the case in incognito mode), in which -// case this will simply return false. -// See PasswordFeatureManager::ShouldShowPasswordStorePicker. -bool ShouldShowPasswordStorePicker(const PrefService* pref_service, - const syncer::SyncService* sync_service); +// Like OptOutOfAccountStorageAndClearSettings(), but applies to a specific +// given |gaia_id| rather than to the current signed-in user. +void OptOutOfAccountStorageAndClearSettingsForAccount( + PrefService* pref_service, + const std::string& gaia_id); + +// Whether it makes sense to ask the user to move a password to their account or +// about the store when saving a password (i.e. profile or account store). This +// is true if the user has opted in already, or hasn't opted in but all other +// requirements are met (i.e. there is a signed-in user, Sync-the-feature is not +// enabled, etc). |pref_service| must not be null. |sync_service| may be null +// (commonly the case in incognito mode), in which case this will simply return +// false. See PasswordFeatureManager::ShouldShowPasswordStorePicker. +bool ShouldShowAccountStorageBubbleUi(const PrefService* pref_service, + const syncer::SyncService* sync_service); // Sets the default storage location for signed-in but non-syncing users. This // store is used for saving new credentials and adding blacking listing entries. @@ -91,6 +96,14 @@ autofill::PasswordForm::Store GetDefaultPasswordStore( const PrefService* pref_service, const syncer::SyncService* sync_service); +// Clears all account-storage-related settings for all users *except* the ones +// in the passed-in |gaia_ids|. Most notably, this includes the opt-in, but also +// all other related settings like the default password store. +// |pref_service| must not be null. +void KeepAccountStorageSettingsOnlyForUsers( + PrefService* pref_service, + const std::vector<std::string>& gaia_ids); + // Clears all account-storage-related settings for all users. Most notably, this // includes the opt-in, but also all other related settings like the default // password store. Meant to be called when account cookies were cleared. diff --git a/chromium/components/password_manager/core/browser/password_manager_features_util_unittest.cc b/chromium/components/password_manager/core/browser/password_manager_features_util_unittest.cc index f1a5f9c7c2b..261f11ace51 100644 --- a/chromium/components/password_manager/core/browser/password_manager_features_util_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_manager_features_util_unittest.cc @@ -167,7 +167,7 @@ TEST(PasswordFeatureManagerUtil, AccountStoragePerAccountSettings) { // Initially the user is not signed in, so everything is off/local. EXPECT_FALSE(IsOptedInForAccountStorage(&pref_service, &sync_service)); EXPECT_FALSE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service)); - EXPECT_FALSE(ShouldShowPasswordStorePicker(&pref_service, &sync_service)); + EXPECT_FALSE(ShouldShowAccountStorageBubbleUi(&pref_service, &sync_service)); EXPECT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service), autofill::PasswordForm::Store::kProfileStore); @@ -220,6 +220,55 @@ TEST(PasswordFeatureManagerUtil, AccountStoragePerAccountSettings) { autofill::PasswordForm::Store::kProfileStore); } +TEST(PasswordFeatureManagerUtil, AccountStorageKeepSettingsOnlyForUsers) { + base::test::ScopedFeatureList features; + features.InitAndEnableFeature(features::kEnablePasswordsAccountStorage); + + TestingPrefServiceSimple pref_service; + pref_service.registry()->RegisterDictionaryPref( + prefs::kAccountStoragePerAccountSettings); + + CoreAccountInfo first_account; + first_account.email = "first@account.com"; + first_account.gaia = "first"; + first_account.account_id = CoreAccountId::FromGaiaId(first_account.gaia); + + CoreAccountInfo second_account; + second_account.email = "second@account.com"; + second_account.gaia = "second"; + second_account.account_id = CoreAccountId::FromGaiaId(second_account.gaia); + + syncer::TestSyncService sync_service; + sync_service.SetDisableReasons({}); + sync_service.SetIsAuthenticatedAccountPrimary(false); + sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE); + + // Let SyncService run in transport mode with |first_account| and opt in. + sync_service.SetAuthenticatedAccountInfo(first_account); + OptInToAccountStorage(&pref_service, &sync_service); + ASSERT_TRUE(IsOptedInForAccountStorage(&pref_service, &sync_service)); + + // Switch to |second_account| and again opt in. + sync_service.SetAuthenticatedAccountInfo(second_account); + OptInToAccountStorage(&pref_service, &sync_service); + ASSERT_TRUE(IsOptedInForAccountStorage(&pref_service, &sync_service)); + + // Sign out. The opt-in still exists, but doesn't apply anymore. + sync_service.SetAuthenticatedAccountInfo(CoreAccountInfo()); + ASSERT_FALSE(IsOptedInForAccountStorage(&pref_service, &sync_service)); + + // Keep the opt-in only for |first_account| (and some unknown other user). + KeepAccountStorageSettingsOnlyForUsers(&pref_service, + {first_account.gaia, "other_gaia_id"}); + + // The first account should still be opted in, but not the second. + sync_service.SetAuthenticatedAccountInfo(first_account); + EXPECT_TRUE(IsOptedInForAccountStorage(&pref_service, &sync_service)); + + sync_service.SetAuthenticatedAccountInfo(second_account); + EXPECT_FALSE(IsOptedInForAccountStorage(&pref_service, &sync_service)); +} + TEST(PasswordFeatureManagerUtil, SyncSuppressesAccountStorageOptIn) { base::test::ScopedFeatureList features; features.InitAndEnableFeature(features::kEnablePasswordsAccountStorage); @@ -245,7 +294,7 @@ TEST(PasswordFeatureManagerUtil, SyncSuppressesAccountStorageOptIn) { // In this state, the user could opt in to the account storage. ASSERT_FALSE(IsOptedInForAccountStorage(&pref_service, &sync_service)); ASSERT_TRUE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service)); - ASSERT_TRUE(ShouldShowPasswordStorePicker(&pref_service, &sync_service)); + ASSERT_TRUE(ShouldShowAccountStorageBubbleUi(&pref_service, &sync_service)); // Now the user enables Sync-the-feature. sync_service.SetIsAuthenticatedAccountPrimary(true); @@ -255,7 +304,7 @@ TEST(PasswordFeatureManagerUtil, SyncSuppressesAccountStorageOptIn) { // Now the account-storage opt-in should *not* be available anymore. EXPECT_FALSE(IsOptedInForAccountStorage(&pref_service, &sync_service)); EXPECT_FALSE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service)); - EXPECT_FALSE(ShouldShowPasswordStorePicker(&pref_service, &sync_service)); + EXPECT_FALSE(ShouldShowAccountStorageBubbleUi(&pref_service, &sync_service)); } TEST(PasswordFeatureManagerUtil, SyncDisablesAccountStorage) { @@ -283,7 +332,7 @@ TEST(PasswordFeatureManagerUtil, SyncDisablesAccountStorage) { // and saving will default to the account store. ASSERT_FALSE(IsOptedInForAccountStorage(&pref_service, &sync_service)); ASSERT_TRUE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service)); - ASSERT_TRUE(ShouldShowPasswordStorePicker(&pref_service, &sync_service)); + ASSERT_TRUE(ShouldShowAccountStorageBubbleUi(&pref_service, &sync_service)); ASSERT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service), autofill::PasswordForm::Store::kAccountStore); @@ -291,7 +340,7 @@ TEST(PasswordFeatureManagerUtil, SyncDisablesAccountStorage) { OptInToAccountStorage(&pref_service, &sync_service); ASSERT_TRUE(IsOptedInForAccountStorage(&pref_service, &sync_service)); ASSERT_FALSE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service)); - ASSERT_TRUE(ShouldShowPasswordStorePicker(&pref_service, &sync_service)); + ASSERT_TRUE(ShouldShowAccountStorageBubbleUi(&pref_service, &sync_service)); ASSERT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service), autofill::PasswordForm::Store::kAccountStore); @@ -303,7 +352,7 @@ TEST(PasswordFeatureManagerUtil, SyncDisablesAccountStorage) { ASSERT_TRUE(sync_service.IsSyncFeatureEnabled()); EXPECT_TRUE(IsOptedInForAccountStorage(&pref_service, &sync_service)); EXPECT_FALSE(ShouldShowAccountStorageOptIn(&pref_service, &sync_service)); - EXPECT_FALSE(ShouldShowPasswordStorePicker(&pref_service, &sync_service)); + EXPECT_FALSE(ShouldShowAccountStorageBubbleUi(&pref_service, &sync_service)); EXPECT_EQ(GetDefaultPasswordStore(&pref_service, &sync_service), autofill::PasswordForm::Store::kProfileStore); } @@ -347,6 +396,76 @@ TEST(PasswordFeatureManagerUtil, OptOutClearsStorePreference) { histogram_tester.ExpectUniqueSample( "PasswordManager.AccountStorage.SignedInAccountFoundDuringOptOut", true, 1); + // The change to the profile store above should have been recorded. Clearing + // the pref does not get recorded in this histogram! + histogram_tester.ExpectUniqueSample( + "PasswordManager.DefaultPasswordStoreSet", + autofill::PasswordForm::Store::kProfileStore, 1); +} + +TEST(PasswordFeatureManagerUtil, OptInOutHistograms) { + base::test::ScopedFeatureList features; + features.InitAndEnableFeature(features::kEnablePasswordsAccountStorage); + base::HistogramTester histogram_tester; + + TestingPrefServiceSimple pref_service; + pref_service.registry()->RegisterDictionaryPref( + prefs::kAccountStoragePerAccountSettings); + + syncer::TestSyncService sync_service; + sync_service.SetDisableReasons({}); + sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE); + sync_service.SetIsAuthenticatedAccountPrimary(false); + + CoreAccountInfo first_account; + first_account.email = "first@account.com"; + first_account.gaia = "first"; + first_account.account_id = CoreAccountId::FromGaiaId(first_account.gaia); + + CoreAccountInfo second_account; + second_account.email = "second@account.com"; + second_account.gaia = "second"; + second_account.account_id = CoreAccountId::FromGaiaId(second_account.gaia); + + // Opt in with the first account. + sync_service.SetAuthenticatedAccountInfo(first_account); + OptInToAccountStorage(&pref_service, &sync_service); + // There is now 1 opt-in. + histogram_tester.ExpectTotalCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn", 1); + histogram_tester.ExpectBucketCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn", 1, 1); + + // Opt in with the second account. + sync_service.SetAuthenticatedAccountInfo(second_account); + OptInToAccountStorage(&pref_service, &sync_service); + // There are now 2 opt-ins. + histogram_tester.ExpectTotalCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn", 2); + histogram_tester.ExpectBucketCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn", 2, 1); + + // Out out of the second account again. + OptOutOfAccountStorageAndClearSettings(&pref_service, &sync_service); + // The OptedIn histogram is unchanged. + histogram_tester.ExpectTotalCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn", 2); + // There is now an OptedOut sample; there is 1 opt-in left. + histogram_tester.ExpectTotalCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptOut", 1); + histogram_tester.ExpectBucketCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptOut", 1, 1); + + // Clear all remaining opt-ins (which is just one). + ClearAccountStorageSettingsForAllUsers(&pref_service); + // The OptedIn/OptedOut histograms are unchanged. + histogram_tester.ExpectTotalCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptIn", 2); + histogram_tester.ExpectTotalCount( + "PasswordManager.AccountStorage.NumOptedInAccountsAfterOptOut", 1); + // There was 1 remaining opt-in that was cleared. + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStorage.ClearedOptInForAllAccounts", 1, 1); } } // namespace features_util diff --git a/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.cc b/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.cc index 8ebf89d0f90..c8f492e88dc 100644 --- a/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.cc +++ b/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.cc @@ -20,11 +20,9 @@ namespace password_manager { PasswordManagerMetricsRecorder::PasswordManagerMetricsRecorder( ukm::SourceId source_id, - const GURL& main_frame_url, std::unique_ptr<NavigationMetricRecorderDelegate> navigation_metric_recorder) - : main_frame_url_(main_frame_url), - ukm_entry_builder_( + : ukm_entry_builder_( std::make_unique<ukm::builders::PageWithPassword>(source_id)), navigation_metric_recorder_(std::move(navigation_metric_recorder)) {} @@ -45,16 +43,14 @@ PasswordManagerMetricsRecorder& PasswordManagerMetricsRecorder::operator=( void PasswordManagerMetricsRecorder::RecordUserModifiedPasswordField() { if (!user_modified_password_field_ && navigation_metric_recorder_) { - navigation_metric_recorder_->OnUserModifiedPasswordFieldFirstTime( - main_frame_url_); + navigation_metric_recorder_->OnUserModifiedPasswordFieldFirstTime(); } user_modified_password_field_ = true; } void PasswordManagerMetricsRecorder::RecordUserFocusedPasswordField() { if (!user_focused_password_field_ && navigation_metric_recorder_) { - navigation_metric_recorder_->OnUserFocusedPasswordFieldFirstTime( - main_frame_url_); + navigation_metric_recorder_->OnUserFocusedPasswordFieldFirstTime(); } user_focused_password_field_ = true; } diff --git a/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.h b/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.h index c83090a5527..94c8e320068 100644 --- a/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.h +++ b/chromium/components/password_manager/core/browser/password_manager_metrics_recorder.h @@ -85,20 +85,16 @@ class PasswordManagerMetricsRecorder { // about the current navigation. class NavigationMetricRecorderDelegate { public: + virtual ~NavigationMetricRecorderDelegate() = default; // Called the first time the user focuses on a password field. - virtual void OnUserFocusedPasswordFieldFirstTime( - const GURL& main_frame_url) = 0; + virtual void OnUserFocusedPasswordFieldFirstTime() = 0; // Called the first time the user types into a password field. - virtual void OnUserModifiedPasswordFieldFirstTime( - const GURL& main_frame_url) = 0; - - virtual ~NavigationMetricRecorderDelegate() = default; + virtual void OnUserModifiedPasswordFieldFirstTime() = 0; }; // Records UKM metrics and reports them on destruction. PasswordManagerMetricsRecorder( ukm::SourceId source_id, - const GURL& main_frame_url, std::unique_ptr<NavigationMetricRecorderDelegate> navigation_metric_recorder); @@ -130,9 +126,6 @@ class PasswordManagerMetricsRecorder { void RecordPageLevelUserAction(PageLevelUserAction action); private: - // URL for which UKMs are reported. - GURL main_frame_url_; - // Records URL keyed metrics (UKMs) and submits them on its destruction. std::unique_ptr<ukm::builders::PageWithPassword> ukm_entry_builder_; diff --git a/chromium/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc b/chromium/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc index 09b7061dc2a..eaa179a8fd7 100644 --- a/chromium/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_manager_metrics_recorder_unittest.cc @@ -21,14 +21,12 @@ namespace password_manager { namespace { -constexpr char kTestUrl[] = "https://www.example.com/"; constexpr ukm::SourceId kTestSourceId = 0x1234; using UkmEntry = ukm::builders::PageWithPassword; -// Creates a PasswordManagerMetricsRecorder that reports metrics for kTestUrl. PasswordManagerMetricsRecorder CreateMetricsRecorder() { - return PasswordManagerMetricsRecorder(kTestSourceId, GURL(kTestUrl), nullptr); + return PasswordManagerMetricsRecorder(kTestSourceId, nullptr); } } // namespace diff --git a/chromium/components/password_manager/core/browser/password_manager_metrics_util.cc b/chromium/components/password_manager/core/browser/password_manager_metrics_util.cc index 25a29b853b3..7b1cef82685 100644 --- a/chromium/components/password_manager/core/browser/password_manager_metrics_util.cc +++ b/chromium/components/password_manager/core/browser/password_manager_metrics_util.cc @@ -8,6 +8,7 @@ #include "base/metrics/histogram_functions.h" #include "base/strings/strcat.h" #include "components/autofill/core/common/password_generation_util.h" +#include "components/password_manager/core/common/password_manager_features.h" using autofill::password_generation::PasswordGenerationType; @@ -57,9 +58,27 @@ void LogGeneralUIDismissalReason(UIDismissalReason reason) { NUM_UI_RESPONSES); } -void LogSaveUIDismissalReason(UIDismissalReason reason) { +void LogSaveUIDismissalReason( + UIDismissalReason reason, + base::Optional<PasswordAccountStorageUserState> user_state) { base::UmaHistogramEnumeration("PasswordManager.SaveUIDismissalReason", reason, NUM_UI_RESPONSES); + + if (user_state.has_value()) { + DCHECK(base::FeatureList::IsEnabled( + password_manager::features::kEnablePasswordsAccountStorage)); + std::string suffix = + GetPasswordAccountStorageUserStateHistogramSuffix(user_state.value()); + base::UmaHistogramEnumeration( + "PasswordManager.SaveUIDismissalReason." + suffix, reason, + NUM_UI_RESPONSES); + } +} + +void LogSaveUIDismissalReasonAfterUnblacklisting(UIDismissalReason reason) { + base::UmaHistogramEnumeration( + "PasswordManager.SaveUIDismissalReasonAfterUnblacklisting", reason, + NUM_UI_RESPONSES); } void LogUpdateUIDismissalReason(UIDismissalReason reason) { @@ -67,6 +86,21 @@ void LogUpdateUIDismissalReason(UIDismissalReason reason) { reason, NUM_UI_RESPONSES); } +void LogMoveUIDismissalReason(UIDismissalReason reason, + PasswordAccountStorageUserState user_state) { + DCHECK(base::FeatureList::IsEnabled( + password_manager::features::kEnablePasswordsAccountStorage)); + + base::UmaHistogramEnumeration("PasswordManager.MoveUIDismissalReason", reason, + NUM_UI_RESPONSES); + + std::string suffix = + GetPasswordAccountStorageUserStateHistogramSuffix(user_state); + base::UmaHistogramEnumeration( + "PasswordManager.MoveUIDismissalReason." + suffix, reason, + NUM_UI_RESPONSES); +} + void LogLeakDialogTypeAndDismissalReason(LeakDialogType type, LeakDialogDismissalReason reason) { static constexpr char kHistogram[] = @@ -221,6 +255,13 @@ void LogSubmittedFormFrame(SubmittedFormFrame frame) { SubmittedFormFrame::SUBMITTED_FORM_FRAME_COUNT); } +void LogPasswordsCountFromAccountStoreAfterUnlock( + int account_store_passwords_count) { + base::UmaHistogramCounts100( + "PasswordManager.CredentialsCountFromAccountStoreAfterUnlock", + account_store_passwords_count); +} + void LogPasswordSettingsReauthResult(ReauthResult result) { base::UmaHistogramEnumeration( "PasswordManager.ReauthToAccessPasswordInSettings", result); @@ -232,11 +273,6 @@ void LogDeleteUndecryptableLoginsReturnValue( "PasswordManager.DeleteUndecryptableLoginsReturnValue", result); } -void LogDeleteCorruptedPasswordsResult(DeleteCorruptedPasswordsResult result) { - base::UmaHistogramEnumeration( - "PasswordManager.DeleteCorruptedPasswordsResult", result); -} - void LogNewlySavedPasswordIsGenerated( bool value, PasswordAccountStorageUsageLevel account_storage_usage_level) { diff --git a/chromium/components/password_manager/core/browser/password_manager_metrics_util.h b/chromium/components/password_manager/core/browser/password_manager_metrics_util.h index 9ad5d5df6ea..dfee676caba 100644 --- a/chromium/components/password_manager/core/browser/password_manager_metrics_util.h +++ b/chromium/components/password_manager/core/browser/password_manager_metrics_util.h @@ -164,7 +164,9 @@ enum class CredentialManagerGetResult { kAutoSignIn, // No credentials are returned in incognito mode. kNoneIncognito, - kMaxValue = kNoneIncognito, + // No credentials are returned while autofill_assistant is running. + kNoneAutofillAssistant, + kMaxValue = kNoneAutofillAssistant, }; enum PasswordReusePasswordFieldDetected { @@ -355,6 +357,8 @@ enum class PasswordDropdownState { }; // Type of the item the user selects in the password drop-down. +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. enum class PasswordDropdownSelectedOption { // User selected a credential to fill. kPassword = 0, @@ -362,7 +366,13 @@ enum class PasswordDropdownSelectedOption { kShowAll = 1, // User selected to generate a password. kGenerate = 2, - kMaxValue = kGenerate + // User unlocked the account-store to fill a password. + kUnlockAccountStorePasswords = 3, + // User unlocked the account-store to generate a password. + kUnlockAccountStoreGeneration = 4, + // Previoulsy opted-in user decided to log-in again to access their passwords. + kResigninToUnlockAccountStore = 5, + kMaxValue = kResigninToUnlockAccountStore }; // Used in UMA histograms, please do NOT reorder. @@ -398,6 +408,20 @@ enum class PasswordAccountStorageUserState { // Syncing user. kSyncUser, }; + +// Metrics: PasswordManager.MoveToAccountStoreTrigger. +// This must be kept in sync with the enum in password_move_to_account_dialog.js +// (in chrome/browser/resources/settings/autofill_page). +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class MoveToAccountStoreTrigger { + // The user successfully logged in with a password from the profile store. + kSuccessfulLoginWithProfileStorePassword = 0, + // The user explicitly asked to move a password listed in Settings. + kExplicitlyTriggeredInSettings = 1, + kMaxValue = kExplicitlyTriggeredInSettings, +}; + std::string GetPasswordAccountStorageUserStateHistogramSuffix( PasswordAccountStorageUserState user_state); @@ -421,12 +445,25 @@ std::string GetPasswordAccountStorageUsageLevelHistogramSuffix( // bubbles. void LogGeneralUIDismissalReason(UIDismissalReason reason); -// Log the |reason| a user dismissed the save password bubble. -void LogSaveUIDismissalReason(UIDismissalReason reason); +// Log the |reason| a user dismissed the save password bubble. If +// |user_state| is set, the |reason| is also logged to a separate +// user-state-specific histogram. |user_state| must be non-null iff the feature +// kEnablePasswordsAccountStorage is enabled. +void LogSaveUIDismissalReason( + UIDismissalReason reason, + base::Optional<PasswordAccountStorageUserState> user_state); + +// Log the |reason| a user dismissed the save password prompt after previously +// having unblacklisted the origin while on the page. +void LogSaveUIDismissalReasonAfterUnblacklisting(UIDismissalReason reason); // Log the |reason| a user dismissed the update password bubble. void LogUpdateUIDismissalReason(UIDismissalReason reason); +// Log the |reason| a user dismissed the move password bubble. +void LogMoveUIDismissalReason(UIDismissalReason reason, + PasswordAccountStorageUserState user_state); + // Log the |type| of a leak dialog shown to the user and the |reason| why it was // dismissed. void LogLeakDialogTypeAndDismissalReason(LeakDialogType type, @@ -500,6 +537,9 @@ void LogPasswordAcceptedSaveUpdateSubmissionIndicatorEvent( // Log a frame of a submitted password form. void LogSubmittedFormFrame(SubmittedFormFrame frame); +// Logs how many account-stored passwords are available right after unlock. +void LogPasswordsCountFromAccountStoreAfterUnlock(int account_store_passwords); + // Logs the result of a re-auth challenge in the password settings. void LogPasswordSettingsReauthResult(ReauthResult result); @@ -507,10 +547,6 @@ void LogPasswordSettingsReauthResult(ReauthResult result); void LogDeleteUndecryptableLoginsReturnValue( DeleteCorruptedPasswordsResult result); -// Log a result of removing passwords that couldn't be decrypted with the -// present encryption key on MacOS. -void LogDeleteCorruptedPasswordsResult(DeleteCorruptedPasswordsResult result); - // Log whether a saved password was generated. void LogNewlySavedPasswordIsGenerated( bool value, diff --git a/chromium/components/password_manager/core/browser/password_manager_onboarding_unittest.cc b/chromium/components/password_manager/core/browser/password_manager_onboarding_unittest.cc index d6ae68ec499..533bd0d8ac5 100644 --- a/chromium/components/password_manager/core/browser/password_manager_onboarding_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_manager_onboarding_unittest.cc @@ -11,6 +11,7 @@ #include "base/bind_helpers.h" #include "base/macros.h" +#include "base/memory/scoped_refptr.h" #include "base/stl_util.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" @@ -36,13 +37,9 @@ namespace password_manager { class PasswordManagerOnboardingTest : public testing::Test { public: - PasswordManagerOnboardingTest() = default; - void SetUp() override { - store_ = new TestPasswordStore; store_->Init(nullptr); - prefs_.reset(new TestingPrefServiceSimple()); prefs_->registry()->RegisterIntegerPref( prefs::kPasswordManagerOnboardingState, static_cast<int>(OnboardingState::kDoNotShow)); @@ -63,7 +60,7 @@ class PasswordManagerOnboardingTest : public testing::Test { PasswordForm MakeSimpleForm(int id) { PasswordForm form; - form.origin = GURL("https://example.org/"); + form.url = GURL("https://example.org/"); form.signon_realm = "https://example.org/"; form.username_value = ASCIIToUTF16("username") + base::NumberToString16(id); form.password_value = ASCIIToUTF16("p4ssword") + base::NumberToString16(id); @@ -73,7 +70,7 @@ class PasswordManagerOnboardingTest : public testing::Test { PasswordForm MakeSimpleBlacklistedForm(int id) { PasswordForm form; std::string link = "https://example" + base::NumberToString(id) + ".org/"; - form.origin = GURL(link); + form.url = GURL(link); form.signon_realm = link; form.blacklisted_by_user = true; return form; @@ -81,8 +78,10 @@ class PasswordManagerOnboardingTest : public testing::Test { protected: base::test::TaskEnvironment task_environment_; - scoped_refptr<TestPasswordStore> store_; - std::unique_ptr<TestingPrefServiceSimple> prefs_; + scoped_refptr<TestPasswordStore> store_ = + base::MakeRefCounted<TestPasswordStore>(); + std::unique_ptr<TestingPrefServiceSimple> prefs_ = + std::make_unique<TestingPrefServiceSimple>(); }; TEST_F(PasswordManagerOnboardingTest, CredentialsCountUnderThreshold) { diff --git a/chromium/components/password_manager/core/browser/password_manager_test_utils.cc b/chromium/components/password_manager/core/browser/password_manager_test_utils.cc index 46f95aa975e..961da21aec2 100644 --- a/chromium/components/password_manager/core/browser/password_manager_test_utils.cc +++ b/chromium/components/password_manager/core/browser/password_manager_test_utils.cc @@ -28,7 +28,7 @@ std::unique_ptr<PasswordForm> PasswordFormFromData( if (form_data.signon_realm) form->signon_realm = std::string(form_data.signon_realm); if (form_data.origin) - form->origin = GURL(form_data.origin); + form->url = GURL(form_data.origin); if (form_data.action) form->action = GURL(form_data.action); if (form_data.submit_element) @@ -72,7 +72,7 @@ std::unique_ptr<autofill::PasswordForm> CreateEntry( auto form = std::make_unique<autofill::PasswordForm>(); form->username_value = base::ASCIIToUTF16(username); form->password_value = base::ASCIIToUTF16(password); - form->origin = origin_url; + form->url = origin_url; form->is_public_suffix_match = is_psl_match; form->is_affiliation_based_match = is_affiliation_based_match; return form; @@ -119,14 +119,16 @@ bool ContainsEqualPasswordFormsUnordered( return !had_mismatched_actual_form && remaining_expectations.empty(); } -MockPasswordStoreObserver::MockPasswordStoreObserver() {} +MockPasswordStoreObserver::MockPasswordStoreObserver() = default; -MockPasswordStoreObserver::~MockPasswordStoreObserver() {} +MockPasswordStoreObserver::~MockPasswordStoreObserver() = default; #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) -MockPasswordReuseDetectorConsumer::MockPasswordReuseDetectorConsumer() {} +MockPasswordReuseDetectorConsumer::MockPasswordReuseDetectorConsumer() = + default; -MockPasswordReuseDetectorConsumer::~MockPasswordReuseDetectorConsumer() {} +MockPasswordReuseDetectorConsumer::~MockPasswordReuseDetectorConsumer() = + default; PasswordHashDataMatcher::PasswordHashDataMatcher( base::Optional<PasswordHashData> expected) diff --git a/chromium/components/password_manager/core/browser/password_manager_unittest.cc b/chromium/components/password_manager/core/browser/password_manager_unittest.cc index e7624b3cab6..a6932409908 100644 --- a/chromium/components/password_manager/core/browser/password_manager_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_manager_unittest.cc @@ -137,32 +137,52 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { ON_CALL(*this, IsNewTabPage()).WillByDefault(Return(false)); } - MOCK_CONST_METHOD1(IsSavingAndFillingEnabled, bool(const GURL&)); - MOCK_CONST_METHOD0(GetMainFrameCertStatus, net::CertStatus()); - MOCK_METHOD2(AutofillHttpAuth, - void(const autofill::PasswordForm&, - const PasswordFormManagerForUI*)); - MOCK_CONST_METHOD0(GetProfilePasswordStore, PasswordStore*()); + MOCK_METHOD(bool, + IsSavingAndFillingEnabled, + (const GURL&), + (const, override)); + MOCK_METHOD(net::CertStatus, GetMainFrameCertStatus, (), (const, override)); + MOCK_METHOD(void, + AutofillHttpAuth, + (const autofill::PasswordForm&, const PasswordFormManagerForUI*), + (override)); + MOCK_METHOD(PasswordStore*, GetProfilePasswordStore, (), (const, override)); + MOCK_METHOD(PasswordStore*, GetAccountPasswordStore, (), (const, override)); // The code inside EXPECT_CALL for PromptUserToSaveOrUpdatePasswordPtr and // ShowManualFallbackForSavingPtr owns the PasswordFormManager* argument. - MOCK_METHOD1(PromptUserToSaveOrUpdatePasswordPtr, - void(PasswordFormManagerForUI*)); - MOCK_METHOD1(ShowOnboarding, bool(std::unique_ptr<PasswordFormManagerForUI>)); - MOCK_METHOD3(ShowManualFallbackForSavingPtr, - void(PasswordFormManagerForUI*, bool, bool)); - MOCK_METHOD0(HideManualFallbackForSaving, void()); - MOCK_METHOD1(NotifySuccessfulLoginWithExistingPassword, - void(std::unique_ptr<PasswordFormManagerForUI>)); - MOCK_METHOD0(AutomaticPasswordSaveIndicator, void()); - MOCK_CONST_METHOD0(GetPrefs, PrefService*()); - MOCK_CONST_METHOD0(GetMainFrameURL, const GURL&()); - MOCK_CONST_METHOD0(IsMainFrameSecure, bool()); - MOCK_METHOD0(GetDriver, PasswordManagerDriver*()); - MOCK_CONST_METHOD0(GetStoreResultFilter, const MockStoreResultFilter*()); - MOCK_METHOD0(GetMetricsRecorder, PasswordManagerMetricsRecorder*()); - MOCK_CONST_METHOD0(IsNewTabPage, bool()); - MOCK_CONST_METHOD0(GetPasswordSyncState, SyncState()); - MOCK_CONST_METHOD0(GetFieldInfoManager, FieldInfoManager*()); + MOCK_METHOD(void, + PromptUserToSaveOrUpdatePasswordPtr, + (PasswordFormManagerForUI*)); + MOCK_METHOD(bool, + ShowOnboarding, + (std::unique_ptr<PasswordFormManagerForUI>), + (override)); + MOCK_METHOD(void, + ShowManualFallbackForSavingPtr, + (PasswordFormManagerForUI*, bool, bool)); + MOCK_METHOD(void, HideManualFallbackForSaving, (), (override)); + MOCK_METHOD(void, + NotifySuccessfulLoginWithExistingPassword, + (std::unique_ptr<PasswordFormManagerForUI>), + (override)); + MOCK_METHOD(void, + AutomaticPasswordSave, + (std::unique_ptr<PasswordFormManagerForUI>), + (override)); + MOCK_METHOD(PrefService*, GetPrefs, (), (const, override)); + MOCK_METHOD(const GURL&, GetLastCommittedURL, (), (const, override)); + MOCK_METHOD(bool, IsCommittedMainFrameSecure, (), (const, override)); + MOCK_METHOD(const MockStoreResultFilter*, + GetStoreResultFilter, + (), + (const, override)); + MOCK_METHOD(PasswordManagerMetricsRecorder*, + GetMetricsRecorder, + (), + (override)); + MOCK_METHOD(bool, IsNewTabPage, (), (const, override)); + MOCK_METHOD(SyncState, GetPasswordSyncState, (), (const, override)); + MOCK_METHOD(FieldInfoManager*, GetFieldInfoManager, (), (const, override)); // Workaround for std::unique_ptr<> lacking a copy constructor. bool PromptUserToSaveOrUpdatePassword( @@ -179,10 +199,6 @@ class MockPasswordManagerClient : public StubPasswordManagerClient { ShowManualFallbackForSavingPtr(manager.release(), has_generated_password, is_update); } - void AutomaticPasswordSave( - std::unique_ptr<PasswordFormManagerForUI> manager) override { - AutomaticPasswordSaveIndicator(); - } void FilterAllResultsForSaving() { EXPECT_CALL(filter_, ShouldSave(_)).WillRepeatedly(Return(false)); @@ -212,14 +228,15 @@ class MockPasswordManagerDriver : public StubPasswordManagerDriver { }; // Invokes the password store consumer with a single copy of |form|. -ACTION_P(InvokeConsumer, form) { +ACTION_P2(InvokeConsumer, store, form) { std::vector<std::unique_ptr<PasswordForm>> result; result.push_back(std::make_unique<PasswordForm>(form)); - arg0->OnGetPasswordStoreResults(std::move(result)); + arg0->OnGetPasswordStoreResultsFrom(store, std::move(result)); } -ACTION(InvokeEmptyConsumerWithForms) { - arg0->OnGetPasswordStoreResults(std::vector<std::unique_ptr<PasswordForm>>()); +ACTION_P(InvokeEmptyConsumerWithForms, store) { + arg0->OnGetPasswordStoreResultsFrom( + store, std::vector<std::unique_ptr<PasswordForm>>()); } ACTION_P(SaveToScopedPtr, scoped) { @@ -307,26 +324,51 @@ class MockFieldInfoManager : public FieldInfoManager { } // namespace -class PasswordManagerTest : public testing::Test { +// The test parameter controls the feature "EnablePasswordsAccountStorage". +class PasswordManagerTest : public testing::TestWithParam<bool> { public: - PasswordManagerTest() - : test_url_("https://www.example.com"), - task_runner_(new TestMockTimeTaskRunner) {} + PasswordManagerTest() : task_runner_(new TestMockTimeTaskRunner) { + bool enable_passwords_account_storage = GetParam(); + if (enable_passwords_account_storage) { + feature_list_.InitAndEnableFeature( + features::kEnablePasswordsAccountStorage); + } else { + feature_list_.InitAndDisableFeature( + features::kEnablePasswordsAccountStorage); + } + } ~PasswordManagerTest() override = default; protected: void SetUp() override { store_ = new MockPasswordStore; - ASSERT_TRUE(store_->Init(nullptr)); + ASSERT_TRUE(store_->Init(/*prefs=*/nullptr)); ON_CALL(client_, GetProfilePasswordStore()) .WillByDefault(Return(store_.get())); EXPECT_CALL(*store_, GetSiteStatsImpl(_)).Times(AnyNumber()); - ON_CALL(client_, GetDriver()).WillByDefault(Return(&driver_)); - manager_.reset(new PasswordManager(&client_)); - password_autofill_manager_.reset( - new PasswordAutofillManager(client_.GetDriver(), nullptr, &client_)); + if (base::FeatureList::IsEnabled( + features::kEnablePasswordsAccountStorage)) { + account_store_ = new MockPasswordStore; + ASSERT_TRUE(account_store_->Init(/*prefs=*/nullptr)); + + ON_CALL(client_, GetAccountPasswordStore()) + .WillByDefault(Return(account_store_.get())); + + // Most tests don't really need the account store, but it'll still get + // queried by MultiStoreFormFetcher, so it needs to return something to + // its consumers. Let the account store return empty results by default, + // so that not every test has to set this up individually. Individual + // tests that do cover the account store can still override this. + ON_CALL(*account_store_, GetLogins(_, _)) + .WillByDefault( + WithArg<1>(InvokeEmptyConsumerWithForms(account_store_.get()))); + } + + manager_ = std::make_unique<PasswordManager>(&client_); + password_autofill_manager_ = + std::make_unique<PasswordAutofillManager>(&driver_, nullptr, &client_); EXPECT_CALL(driver_, GetPasswordManager()) .WillRepeatedly(Return(manager_.get())); @@ -336,15 +378,15 @@ class PasswordManagerTest : public testing::Test { EXPECT_CALL(*store_, IsAbleToSavePasswords()).WillRepeatedly(Return(true)); - ON_CALL(client_, GetMainFrameURL()).WillByDefault(ReturnRef(test_url_)); - ON_CALL(client_, IsMainFrameSecure()).WillByDefault(Return(true)); + ON_CALL(client_, GetLastCommittedURL()).WillByDefault(ReturnRef(test_url_)); + ON_CALL(client_, IsCommittedMainFrameSecure()).WillByDefault(Return(true)); ON_CALL(client_, GetMetricsRecorder()).WillByDefault(Return(nullptr)); ON_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)) .WillByDefault(WithArg<0>(DeletePtr())); ON_CALL(client_, ShowManualFallbackForSavingPtr(_, _, _)) .WillByDefault(WithArg<0>(DeletePtr())); - prefs_.reset(new TestingPrefServiceSimple()); + prefs_ = std::make_unique<TestingPrefServiceSimple>(); prefs_->registry()->RegisterIntegerPref( prefs::kPasswordManagerOnboardingState, static_cast<int>(OnboardingState::kDoNotShow)); @@ -356,6 +398,10 @@ class PasswordManagerTest : public testing::Test { true); prefs_->registry()->RegisterBooleanPref(::prefs::kSafeBrowsingEnhanced, true); + prefs_->registry()->RegisterTimePref( + prefs::kProfileStoreDateLastUsedForFilling, base::Time()); + prefs_->registry()->RegisterTimePref( + prefs::kAccountStoreDateLastUsedForFilling, base::Time()); ON_CALL(client_, GetPrefs()).WillByDefault(Return(prefs_.get())); // When waiting for predictions is on, it makes tests more complicated. @@ -366,13 +412,17 @@ class PasswordManagerTest : public testing::Test { } void TearDown() override { + if (account_store_) { + account_store_->ShutdownOnUIThread(); + account_store_ = nullptr; + } store_->ShutdownOnUIThread(); store_ = nullptr; } PasswordForm MakeSavedForm() { PasswordForm form; - form.origin = GURL("http://www.google.com/a/LoginAuth"); + form.url = GURL("http://www.google.com/a/LoginAuth"); form.action = GURL("http://www.google.com/a/Login"); form.username_element = ASCIIToUTF16("Email"); form.password_element = ASCIIToUTF16("Passwd"); @@ -380,6 +430,7 @@ class PasswordManagerTest : public testing::Test { form.password_value = ASCIIToUTF16("p4ssword"); form.submit_element = ASCIIToUTF16("signIn"); form.signon_realm = "http://www.google.com/"; + form.in_store = PasswordForm::Store::kProfileStore; return form; } @@ -420,8 +471,8 @@ class PasswordManagerTest : public testing::Test { PasswordForm MakeSimpleGAIAForm() { PasswordForm form = MakeSimpleForm(); form.form_data.url = GURL("https://accounts.google.com"); - form.origin = GURL("https://accounts.google.com"); - form.signon_realm = form.origin.spec(); + form.url = GURL("https://accounts.google.com"); + form.signon_realm = form.url.spec(); return form; } @@ -458,45 +509,18 @@ class PasswordManagerTest : public testing::Test { PasswordForm MakeAndroidCredential() { PasswordForm android_form; - android_form.origin = GURL("android://hash@google.com"); + android_form.url = GURL("android://hash@google.com"); android_form.signon_realm = "android://hash@google.com"; android_form.username_value = ASCIIToUTF16("google"); android_form.password_value = ASCIIToUTF16("password"); android_form.is_affiliation_based_match = true; + android_form.in_store = PasswordForm::Store::kProfileStore; return android_form; } - // Reproduction of the form present on twitter's login page. - PasswordForm MakeTwitterLoginForm() { - PasswordForm form; - form.origin = GURL("https://twitter.com/"); - form.action = GURL("https://twitter.com/sessions"); - form.username_element = ASCIIToUTF16("Email"); - form.password_element = ASCIIToUTF16("Passwd"); - form.username_value = ASCIIToUTF16("twitter"); - form.password_value = ASCIIToUTF16("password"); - form.submit_element = ASCIIToUTF16("signIn"); - form.signon_realm = "https://twitter.com/"; - return form; - } - - // Reproduction of the form present on twitter's failed login page. - PasswordForm MakeTwitterFailedLoginForm() { - PasswordForm form; - form.origin = GURL("https://twitter.com/login/error?redirect_after_login"); - form.action = GURL("https://twitter.com/sessions"); - form.username_element = ASCIIToUTF16("EmailField"); - form.password_element = ASCIIToUTF16("PasswdField"); - form.username_value = ASCIIToUTF16("twitter"); - form.password_value = ASCIIToUTF16("password"); - form.submit_element = ASCIIToUTF16("signIn"); - form.signon_realm = "https://twitter.com/"; - return form; - } - PasswordForm MakeSimpleFormWithOnlyUsernameField() { PasswordForm form; - form.origin = GURL("http://www.google.com/a/LoginAuth"); + form.url = GURL("http://www.google.com/a/LoginAuth"); form.username_element = ASCIIToUTF16("Email"); form.submit_element = ASCIIToUTF16("signIn"); form.signon_realm = "http://www.google.com/"; @@ -526,13 +550,13 @@ class PasswordManagerTest : public testing::Test { PasswordForm MakeSimpleCreditCardForm() { PasswordForm form; - form.origin = GURL("https://accounts.google.com"); - form.signon_realm = form.origin.spec(); + form.url = GURL("https://accounts.google.com"); + form.signon_realm = form.url.spec(); form.username_element = ASCIIToUTF16("cc-number"); form.password_element = ASCIIToUTF16("cvc"); form.username_value = ASCIIToUTF16("1234567"); form.password_value = ASCIIToUTF16("123"); - form.form_data.url = form.origin; + form.form_data.url = form.url; FormFieldData field; field.name = form.username_element; @@ -562,19 +586,22 @@ class PasswordManagerTest : public testing::Test { manager()->OnPasswordFormSubmitted(&driver_, form_data); } - const GURL test_url_; + base::test::ScopedFeatureList feature_list_; + + const GURL test_url_{"https://www.example.com"}; base::test::SingleThreadTaskEnvironment task_environment_; scoped_refptr<MockPasswordStore> store_; + scoped_refptr<MockPasswordStore> account_store_; testing::NiceMock<MockPasswordManagerClient> client_; MockPasswordManagerDriver driver_; + std::unique_ptr<TestingPrefServiceSimple> prefs_; std::unique_ptr<PasswordAutofillManager> password_autofill_manager_; std::unique_ptr<PasswordManager> manager_; scoped_refptr<TestMockTimeTaskRunner> task_runner_; - std::unique_ptr<TestingPrefServiceSimple> prefs_; }; MATCHER_P(FormMatches, form, "") { - return form.signon_realm == arg.signon_realm && form.origin == arg.origin && + return form.signon_realm == arg.signon_realm && form.url == arg.url && form.action == arg.action && form.username_element == arg.username_element && form.username_value == arg.username_value && @@ -583,7 +610,7 @@ MATCHER_P(FormMatches, form, "") { form.new_password_element == arg.new_password_element; } -TEST_F(PasswordManagerTest, FormSubmitWithOnlyNewPasswordField) { +TEST_P(PasswordManagerTest, FormSubmitWithOnlyNewPasswordField) { // Test that when a form only contains a "new password" field, the form gets // saved and in password store, the new password value is saved as a current // password value. @@ -591,11 +618,11 @@ TEST_F(PasswordManagerTest, FormSubmitWithOnlyNewPasswordField) { PasswordForm form(MakeFormWithOnlyNewPasswordField()); observed.push_back(form.form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -622,13 +649,13 @@ TEST_F(PasswordManagerTest, FormSubmitWithOnlyNewPasswordField) { EXPECT_THAT(saved_form, FormMatches(expected_form)); } -TEST_F(PasswordManagerTest, GeneratedPasswordFormSubmitEmptyStore) { +TEST_P(PasswordManagerTest, GeneratedPasswordFormSubmitEmptyStore) { // Test that generated passwords are stored without asking the user. std::vector<FormData> observed; FormData form_data(MakeFormDataWithOnlyNewPasswordField()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -648,7 +675,7 @@ TEST_F(PasswordManagerTest, GeneratedPasswordFormSubmitEmptyStore) { PasswordForm form_to_save; EXPECT_CALL(*store_, UpdateLoginWithPrimaryKey(_, _)) .WillOnce(SaveArg<0>(&form_to_save)); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()); + EXPECT_CALL(client_, AutomaticPasswordSave); // Now the password manager waits for the navigation to complete. observed.clear(); @@ -661,7 +688,7 @@ TEST_F(PasswordManagerTest, GeneratedPasswordFormSubmitEmptyStore) { } #if defined(OS_IOS) -TEST_F(PasswordManagerTest, EditingGeneratedPasswordOnIOS) { +TEST_P(PasswordManagerTest, EditingGeneratedPasswordOnIOS) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); @@ -669,12 +696,12 @@ TEST_F(PasswordManagerTest, EditingGeneratedPasswordOnIOS) { base::string16 username = form_data.fields[0].value; base::string16 generated_password = form_data.fields[1].value + ASCIIToUTF16("1"); - const base::string16 username_element = form_data.fields[0].name; - const base::string16 generation_element = form_data.fields[1].name; + FieldRendererId username_element = form_data.fields[0].unique_renderer_id; + FieldRendererId generation_element = form_data.fields[1].unique_renderer_id; // A form is found by PasswordManager. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form_data}); // The user is generating the password. The password has to be presaved. @@ -695,7 +722,7 @@ TEST_F(PasswordManagerTest, EditingGeneratedPasswordOnIOS) { FormHasUniqueKey(presaved_form))) .WillOnce(SaveArg<0>(&presaved_form)); - manager()->UpdateStateOnUserInput(&driver_, form_data.name, + manager()->UpdateStateOnUserInput(&driver_, form_data.unique_renderer_id, generation_element, generated_password); Mock::VerifyAndClearExpectations(store_.get()); @@ -707,11 +734,11 @@ TEST_F(PasswordManagerTest, EditingGeneratedPasswordOnIOS) { FormUsernamePasswordAre(username, generated_password), FormHasUniqueKey(presaved_form))); - manager()->UpdateStateOnUserInput(&driver_, form_data.name, username_element, - username); + manager()->UpdateStateOnUserInput(&driver_, form_data.unique_renderer_id, + username_element, username); } -TEST_F(PasswordManagerTest, SavingGeneratedPasswordOnIOS) { +TEST_P(PasswordManagerTest, SavingGeneratedPasswordOnIOS) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); @@ -719,11 +746,11 @@ TEST_F(PasswordManagerTest, SavingGeneratedPasswordOnIOS) { const base::string16 username = form_data.fields[0].value; base::string16 generated_password = form_data.fields[1].value + ASCIIToUTF16("1"); - const base::string16 generation_element = form_data.fields[1].name; + FieldRendererId generation_element = form_data.fields[1].unique_renderer_id; // A form is found by PasswordManager. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form_data}); // The user is generating the password. @@ -734,7 +761,7 @@ TEST_F(PasswordManagerTest, SavingGeneratedPasswordOnIOS) { EXPECT_CALL(*store_, UpdateLoginWithPrimaryKey(_, _)); // Test when the user is changing the generated password. - manager()->UpdateStateOnUserInput(&driver_, form_data.name, + manager()->UpdateStateOnUserInput(&driver_, form_data.unique_renderer_id, generation_element, generated_password); // The user is submitting the form. @@ -748,23 +775,23 @@ TEST_F(PasswordManagerTest, SavingGeneratedPasswordOnIOS) { UpdateLoginWithPrimaryKey( FormUsernamePasswordAre(username, generated_password), _)); EXPECT_CALL(*store_, IsAbleToSavePasswords()).WillRepeatedly(Return(true)); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()); + EXPECT_CALL(client_, AutomaticPasswordSave); // Now the password manager waits for the navigation to complete. manager()->OnPasswordFormsRendered(&driver_, {}, true); } -TEST_F(PasswordManagerTest, PasswordNoLongerGeneratedOnIOS) { +TEST_P(PasswordManagerTest, PasswordNoLongerGeneratedOnIOS) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); FormData form_data = MakeSimpleFormData(); const base::string16 generated_password = form_data.fields[1].value; - const base::string16 generation_element = form_data.fields[1].name; + FieldRendererId generation_element = form_data.fields[1].unique_renderer_id; // A form is found by PasswordManager. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form_data}); // The user is generating the password. @@ -778,15 +805,15 @@ TEST_F(PasswordManagerTest, PasswordNoLongerGeneratedOnIOS) { manager()->OnPasswordNoLongerGenerated(&driver_); } -TEST_F(PasswordManagerTest, ShowHideManualFallbackOnIOS) { +TEST_P(PasswordManagerTest, ShowHideManualFallbackOnIOS) { ON_CALL(client_, IsSavingAndFillingEnabled(_)).WillByDefault(Return(true)); FormData form_data = MakeSimpleFormData(); - const base::string16 password_element = form_data.fields[1].name; + FieldRendererId password_element = form_data.fields[1].unique_renderer_id; // A form is found by PasswordManager. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form_data}); // Check that the saving manual fallback is shown the user typed in a password @@ -795,8 +822,8 @@ TEST_F(PasswordManagerTest, ShowHideManualFallbackOnIOS) { EXPECT_CALL(client_, ShowManualFallbackForSavingPtr(_, false, false)) .WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save))); base::string16 typed_password = ASCIIToUTF16("password"); - manager()->UpdateStateOnUserInput(&driver_, form_data.name, password_element, - typed_password); + manager()->UpdateStateOnUserInput(&driver_, form_data.unique_renderer_id, + password_element, typed_password); Mock::VerifyAndClearExpectations(&client_); ASSERT_TRUE(form_manager_to_save); @@ -806,12 +833,12 @@ TEST_F(PasswordManagerTest, ShowHideManualFallbackOnIOS) { // Check that the saving manual is hidden when the user cleared the password // field value. EXPECT_CALL(client_, HideManualFallbackForSaving()); - manager()->UpdateStateOnUserInput(&driver_, form_data.name, password_element, - base::string16()); + manager()->UpdateStateOnUserInput(&driver_, form_data.unique_renderer_id, + password_element, base::string16()); } #endif // defined(OS_IOS) -TEST_F(PasswordManagerTest, FormSubmitNoGoodMatch) { +TEST_P(PasswordManagerTest, FormSubmitNoGoodMatch) { // When the password store already contains credentials for a given form, new // credentials get still added, as long as they differ in username from the // stored ones. @@ -824,10 +851,10 @@ TEST_F(PasswordManagerTest, FormSubmitNoGoodMatch) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(PasswordStore::FormDigest(form), _)) - .WillOnce(WithArg<1>(InvokeConsumer(existing_different))); + .WillOnce(WithArg<1>(InvokeConsumer(store_.get(), existing_different))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -849,15 +876,15 @@ TEST_F(PasswordManagerTest, FormSubmitNoGoodMatch) { } // Tests that a credential wouldn't be saved if it is already in the store. -TEST_F(PasswordManagerTest, DontSaveAlreadySavedCredential) { +TEST_P(PasswordManagerTest, DontSaveAlreadySavedCredential) { PasswordForm form(MakeSimpleForm()); std::vector<FormData> observed = {form.form_data}; - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(2); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -898,14 +925,14 @@ TEST_F(PasswordManagerTest, DontSaveAlreadySavedCredential) { } // Tests that on Chrome sign-in form credentials are not saved. -TEST_F(PasswordManagerTest, DoNotSaveOnChromeSignInForm) { +TEST_P(PasswordManagerTest, DoNotSaveOnChromeSignInForm) { FormData form_data(MakeSimpleFormData()); form_data.is_gaia_with_skip_save_password_form = true; std::vector<FormData> observed = {form_data}; EXPECT_CALL(client_, IsSavingAndFillingEnabled(form_data.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -927,15 +954,15 @@ TEST_F(PasswordManagerTest, DoNotSaveOnChromeSignInForm) { // Tests that a UKM metric "Login Passed" is sent when the submitted credentials // are already in the store and OnPasswordFormsParsed is called multiple times. -TEST_F(PasswordManagerTest, +TEST_P(PasswordManagerTest, SubmissionMetricsIsPassedWhenDontSaveAlreadySavedCredential) { std::vector<FormData> observed; PasswordForm form(MakeSimpleForm()); observed.push_back(form.form_data); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(AnyNumber()); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -963,12 +990,12 @@ TEST_F(PasswordManagerTest, user_action_tester.GetActionCount("PasswordManager_LoginPassed")); } -TEST_F(PasswordManagerTest, FormSeenThenLeftPage) { +TEST_P(PasswordManagerTest, FormSeenThenLeftPage) { std::vector<FormData> observed; FormData form_data(MakeSimpleFormData()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -980,18 +1007,18 @@ TEST_F(PasswordManagerTest, FormSeenThenLeftPage) { manager()->OnPasswordFormsRendered(&driver_, observed, true); } -TEST_F(PasswordManagerTest, FormSubmit) { +TEST_P(PasswordManagerTest, FormSubmit) { // Test that a plain form submit results in offering to save passwords. PasswordForm form(MakeSimpleForm()); std::vector<FormData> observed = {form.form_data}; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); EXPECT_FALSE(manager()->IsPasswordFieldDetectedOnPage()); manager()->OnPasswordFormsParsed(&driver_, observed); EXPECT_TRUE(manager()->IsPasswordFieldDetectedOnPage()); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -1009,7 +1036,7 @@ TEST_F(PasswordManagerTest, FormSubmit) { form_manager_to_save->Save(); } -TEST_F(PasswordManagerTest, OnboardingSimple) { +TEST_P(PasswordManagerTest, OnboardingSimple) { // Test that a plain form submit results in showing the onboarding // if the |kShouldShow| state is set. ON_CALL(client_, GetPasswordSyncState()) @@ -1023,7 +1050,7 @@ TEST_F(PasswordManagerTest, OnboardingSimple) { FormData form_data(MakeSimpleFormData()); std::vector<FormData> observed = {form_data}; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1040,7 +1067,7 @@ TEST_F(PasswordManagerTest, OnboardingSimple) { manager()->OnPasswordFormsRendered(&driver_, observed, true); } -TEST_F(PasswordManagerTest, OnboardingPasswordSyncDisabled) { +TEST_P(PasswordManagerTest, OnboardingPasswordSyncDisabled) { // Tests that the onboarding is not shown when password sync is disabled. ON_CALL(client_, GetPasswordSyncState()).WillByDefault(Return(NOT_SYNCING)); base::test::ScopedFeatureList feature_list; @@ -1052,7 +1079,7 @@ TEST_F(PasswordManagerTest, OnboardingPasswordSyncDisabled) { FormData form_data(MakeSimpleFormData()); std::vector<FormData> observed = {form_data}; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1071,7 +1098,7 @@ TEST_F(PasswordManagerTest, OnboardingPasswordSyncDisabled) { manager()->OnPasswordFormsRendered(&driver_, observed, true); } -TEST_F(PasswordManagerTest, OnboardingPasswordUpdate) { +TEST_P(PasswordManagerTest, OnboardingPasswordUpdate) { // Tests that the onboarding is not shown on password update. ON_CALL(client_, GetPasswordSyncState()) .WillByDefault(Return(SYNCING_NORMAL_ENCRYPTION)); @@ -1088,7 +1115,8 @@ TEST_F(PasswordManagerTest, OnboardingPasswordUpdate) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(MakeSavedForm()))); + .WillRepeatedly( + WithArg<1>(InvokeConsumer(store_.get(), MakeSavedForm()))); manager()->OnPasswordFormsParsed(&driver_, observed_forms); manager()->OnPasswordFormsRendered(&driver_, observed_forms, true); @@ -1110,10 +1138,10 @@ TEST_F(PasswordManagerTest, OnboardingPasswordUpdate) { manager()->OnPasswordFormsRendered(&driver_, observed_forms, true); } -TEST_F(PasswordManagerTest, IsPasswordFieldDetectedOnPage) { +TEST_P(PasswordManagerTest, IsPasswordFieldDetectedOnPage) { FormData form_data(MakeSimpleFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); EXPECT_FALSE(manager()->IsPasswordFieldDetectedOnPage()); manager()->OnPasswordFormsParsed(&driver_, {form_data}); EXPECT_TRUE(manager()->IsPasswordFieldDetectedOnPage()); @@ -1121,14 +1149,14 @@ TEST_F(PasswordManagerTest, IsPasswordFieldDetectedOnPage) { EXPECT_FALSE(manager()->IsPasswordFieldDetectedOnPage()); } -TEST_F(PasswordManagerTest, FormSubmitWhenPasswordsCannotBeSaved) { +TEST_P(PasswordManagerTest, FormSubmitWhenPasswordsCannotBeSaved) { // Test that a plain form submit doesn't result in offering to save passwords. EXPECT_CALL(*store_, IsAbleToSavePasswords()).WillOnce(Return(false)); FormData form_data(MakeSimpleFormData()); std::vector<FormData> observed = {form_data}; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); EXPECT_FALSE(manager()->IsPasswordFieldDetectedOnPage()); manager()->OnPasswordFormsParsed(&driver_, observed); EXPECT_TRUE(manager()->IsPasswordFieldDetectedOnPage()); @@ -1147,16 +1175,16 @@ TEST_F(PasswordManagerTest, FormSubmitWhenPasswordsCannotBeSaved) { } // This test verifies a fix for http://crbug.com/236673 -TEST_F(PasswordManagerTest, FormSubmitWithFormOnPreviousPage) { +TEST_P(PasswordManagerTest, FormSubmitWithFormOnPreviousPage) { PasswordForm first_form(MakeSimpleForm()); - first_form.origin = GURL("http://www.nytimes.com/"); - first_form.form_data.url = first_form.origin; + first_form.url = GURL("http://www.nytimes.com/"); + first_form.form_data.url = first_form.url; first_form.action = GURL("https://myaccount.nytimes.com/auth/login"); first_form.form_data.action = first_form.action; first_form.signon_realm = "http://www.nytimes.com/"; PasswordForm second_form(MakeSimpleForm()); - second_form.origin = GURL("https://myaccount.nytimes.com/auth/login"); - second_form.form_data.url = second_form.origin; + second_form.url = GURL("https://myaccount.nytimes.com/auth/login"); + second_form.form_data.url = second_form.url; second_form.action = GURL("https://myaccount.nytimes.com/auth/login"); second_form.form_data.action = second_form.action; second_form.signon_realm = "https://myaccount.nytimes.com/"; @@ -1165,7 +1193,7 @@ TEST_F(PasswordManagerTest, FormSubmitWithFormOnPreviousPage) { std::vector<FormData> observed; observed.push_back(first_form.form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); observed.clear(); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1180,7 +1208,7 @@ TEST_F(PasswordManagerTest, FormSubmitWithFormOnPreviousPage) { manager()->OnPasswordFormsRendered(&driver_, observed, true); // Now submit this form - EXPECT_CALL(client_, IsSavingAndFillingEnabled(second_form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(second_form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(second_form.form_data); @@ -1199,18 +1227,18 @@ TEST_F(PasswordManagerTest, FormSubmitWithFormOnPreviousPage) { form_manager_to_save->Save(); } -TEST_F(PasswordManagerTest, FormSubmitInvisibleLogin) { +TEST_P(PasswordManagerTest, FormSubmitInvisibleLogin) { // Tests fix of http://crbug.com/28911: if the login form reappears on the // subsequent page, but is invisible, it shouldn't count as a failed login. std::vector<FormData> observed; PasswordForm form(MakeSimpleForm()); observed.push_back(form.form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -1230,7 +1258,7 @@ TEST_F(PasswordManagerTest, FormSubmitInvisibleLogin) { form_manager_to_save->Save(); } -TEST_F(PasswordManagerTest, InitiallyInvisibleForm) { +TEST_P(PasswordManagerTest, InitiallyInvisibleForm) { // Make sure an invisible login form still gets autofilled. PasswordForm form(MakeSimpleForm()); std::vector<FormData> observed; @@ -1240,17 +1268,17 @@ TEST_F(PasswordManagerTest, InitiallyInvisibleForm) { // the // old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); manager()->OnPasswordFormsParsed(&driver_, observed); observed.clear(); manager()->OnPasswordFormsRendered(&driver_, observed, true); } -TEST_F(PasswordManagerTest, FillPasswordsOnDisabledManager) { +TEST_P(PasswordManagerTest, FillPasswordsOnDisabledManager) { // Test fix for http://crbug.com/158296: Passwords must be filled even if the // password manager is disabled. PasswordForm form(MakeSimpleForm()); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(false)); std::vector<FormData> observed; observed.push_back(form.form_data); @@ -1258,18 +1286,18 @@ TEST_F(PasswordManagerTest, FillPasswordsOnDisabledManager) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); manager()->OnPasswordFormsParsed(&driver_, observed); } -TEST_F(PasswordManagerTest, PasswordFormReappearance) { +TEST_P(PasswordManagerTest, PasswordFormReappearance) { // If the password form reappears after submit, PasswordManager should deduce // that the login failed and not offer saving. std::vector<FormData> observed; FormData login_form_data(MakeSimpleFormData()); observed.push_back(login_form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1289,7 +1317,7 @@ TEST_F(PasswordManagerTest, PasswordFormReappearance) { // A PasswordForm appears, and is visible in the layout: // No expected calls to the PasswordStore... EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)).Times(0); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()).Times(0); + EXPECT_CALL(client_, AutomaticPasswordSave).Times(0); EXPECT_CALL(*store_, AddLogin(_)).Times(0); EXPECT_CALL(*store_, UpdateLogin(_)).Times(0); EXPECT_CALL(*store_, UpdateLoginWithPrimaryKey(_, _)).Times(0); @@ -1297,13 +1325,13 @@ TEST_F(PasswordManagerTest, PasswordFormReappearance) { manager()->OnPasswordFormsRendered(&driver_, observed, true); } -TEST_F(PasswordManagerTest, SyncCredentialsNotSaved) { +TEST_P(PasswordManagerTest, SyncCredentialsNotSaved) { // Simulate loading a simple form with no existing stored password. std::vector<FormData> observed; FormData form_data(MakeSimpleGAIAFormData()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1333,11 +1361,11 @@ TEST_F(PasswordManagerTest, SyncCredentialsNotSaved) { } #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) -TEST_F(PasswordManagerTest, HashSavedOnGaiaFormWithSkipSavePassword) { +TEST_P(PasswordManagerTest, HashSavedOnGaiaFormWithSkipSavePassword) { for (bool did_stop_loading : {false, true}) { SCOPED_TRACE(testing::Message("did_stop_loading = ") << did_stop_loading); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(0); std::vector<FormData> observed; FormData form_data(MakeSimpleGAIAFormData()); @@ -1376,10 +1404,10 @@ TEST_F(PasswordManagerTest, HashSavedOnGaiaFormWithSkipSavePassword) { } } -TEST_F(PasswordManagerTest, +TEST_P(PasswordManagerTest, HashSavedOnGaiaFormWithSkipSavePasswordAndToNTPNavigation) { EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(0); FormData form_data(MakeSimpleGAIAFormData()); // Simulate that this is Gaia form that should be ignored for @@ -1411,7 +1439,7 @@ TEST_F(PasswordManagerTest, // On a successful login with an updated password, // CredentialsFilter::ReportFormLoginSuccess and CredentialsFilter::ShouldSave // should be called. The argument of ShouldSave should be the submitted form. -TEST_F(PasswordManagerTest, ReportFormLoginSuccessAndShouldSaveCalled) { +TEST_P(PasswordManagerTest, ReportFormLoginSuccessAndShouldSaveCalled) { PasswordForm stored_form(MakeSimpleForm()); std::vector<FormData> observed; @@ -1424,14 +1452,14 @@ TEST_F(PasswordManagerTest, ReportFormLoginSuccessAndShouldSaveCalled) { observed.push_back(observed_form.form_data); // Simulate that |form| is already in the store, making this an update. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(stored_form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), stored_form))); EXPECT_CALL(driver_, FillPasswordForm(_)); manager()->OnPasswordFormsParsed(&driver_, observed); EXPECT_CALL(driver_, FillPasswordForm(_)); manager()->OnPasswordFormsRendered(&driver_, observed, true); // Submit form and finish navigation. - EXPECT_CALL(client_, IsSavingAndFillingEnabled(observed_form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(observed_form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(observed_form.form_data); @@ -1452,10 +1480,10 @@ TEST_F(PasswordManagerTest, ReportFormLoginSuccessAndShouldSaveCalled) { // When there is a sync password saved, and the user successfully uses the // stored version of it, PasswordManager should not drop that password. -TEST_F(PasswordManagerTest, SyncCredentialsNotDroppedIfUpToDate) { +TEST_P(PasswordManagerTest, SyncCredentialsNotDroppedIfUpToDate) { PasswordForm form(MakeSimpleGAIAForm()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); client_.FilterAllResultsForSaving(); @@ -1466,7 +1494,7 @@ TEST_F(PasswordManagerTest, SyncCredentialsNotDroppedIfUpToDate) { manager()->OnPasswordFormsRendered(&driver_, observed, true); // Submit form and finish navigation. - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) @@ -1492,11 +1520,11 @@ TEST_F(PasswordManagerTest, SyncCredentialsNotDroppedIfUpToDate) { // While sync credentials are not saved, they are still filled to avoid users // thinking they lost access to their accounts. -TEST_F(PasswordManagerTest, SyncCredentialsStillFilled) { +TEST_P(PasswordManagerTest, SyncCredentialsStillFilled) { PasswordForm form(MakeSimpleForm()); // Pretend that the password store contains credentials stored for |form|. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); client_.FilterAllResultsForSaving(); @@ -1509,7 +1537,7 @@ TEST_F(PasswordManagerTest, SyncCredentialsStillFilled) { EXPECT_EQ(form.password_value, form_data.password_field.value); } -TEST_F(PasswordManagerTest, +TEST_P(PasswordManagerTest, ShouldBlockPasswordForSameOriginButDifferentSchemeTest) { constexpr struct { const char* old_origin; @@ -1535,7 +1563,7 @@ TEST_F(PasswordManagerTest, for (const auto& test_case : kTestData) { SCOPED_TRACE(testing::Message("#test_case = ") << (&test_case - kTestData)); - manager()->main_frame_url_ = GURL(test_case.old_origin); + manager()->submitted_form_url_ = GURL(test_case.old_origin); GURL origin = GURL(test_case.new_origin); EXPECT_EQ( test_case.result, @@ -1545,11 +1573,11 @@ TEST_F(PasswordManagerTest, // Tests whether two submissions to the same origin but different schemes // result in only saving the first submission, which has a secure scheme. -TEST_F(PasswordManagerTest, AttemptedSavePasswordSameOriginInsecureScheme) { +TEST_P(PasswordManagerTest, AttemptedSavePasswordSameOriginInsecureScheme) { PasswordForm secure_form(MakeSimpleForm()); - secure_form.origin = GURL("https://example.com/login"); + secure_form.url = GURL("https://example.com/login"); secure_form.action = GURL("https://example.com/login"); - secure_form.form_data.url = secure_form.origin; + secure_form.form_data.url = secure_form.url; secure_form.form_data.action = secure_form.action; secure_form.signon_realm = "https://example.com/"; @@ -1565,21 +1593,18 @@ TEST_F(PasswordManagerTest, AttemptedSavePasswordSameOriginInsecureScheme) { insecure_form.password_value = ASCIIToUTF16("C0mpr0m1s3d_P4ss"); FormFieldData& password_field = insecure_form.form_data.fields[1]; password_field.value = insecure_form.password_value; - insecure_form.origin = GURL("http://example.com/home"); + insecure_form.url = GURL("http://example.com/home"); insecure_form.action = GURL("http://example.com/home"); - insecure_form.form_data.url = insecure_form.origin; + insecure_form.form_data.url = insecure_form.url; insecure_form.form_data.action = insecure_form.action; insecure_form.signon_realm = "http://example.com/"; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(secure_form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(secure_form.url)) .WillRepeatedly(Return(true)); - EXPECT_CALL(client_, GetMainFrameURL()) - .WillRepeatedly(ReturnRef(secure_form.origin)); - // Parse, render and submit the secure form. std::vector<FormData> observed = {secure_form.form_data}; manager()->OnPasswordFormsParsed(&driver_, observed); @@ -1592,12 +1617,9 @@ TEST_F(PasswordManagerTest, AttemptedSavePasswordSameOriginInsecureScheme) { EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)) .WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save))); - EXPECT_CALL(client_, GetMainFrameURL()) - .WillRepeatedly(ReturnRef(insecure_form.origin)); - // Parse, render and submit the insecure form. observed = {insecure_form.form_data}; - EXPECT_CALL(client_, IsSavingAndFillingEnabled(insecure_form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(insecure_form.url)) .WillRepeatedly(Return(true)); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1625,7 +1647,7 @@ TEST_F(PasswordManagerTest, AttemptedSavePasswordSameOriginInsecureScheme) { // password forms, that would be the reasonable choice), if the new password is // empty, this is likely just a slightly misunderstood form, and Chrome should // save the non-empty current password field. -TEST_F(PasswordManagerTest, DoNotSaveWithEmptyNewPasswordAndNonemptyPassword) { +TEST_P(PasswordManagerTest, DoNotSaveWithEmptyNewPasswordAndNonemptyPassword) { std::vector<FormData> observed; FormData form_data(MakeSimpleFormData()); ASSERT_FALSE(form_data.fields[1].value.empty()); @@ -1639,7 +1661,7 @@ TEST_F(PasswordManagerTest, DoNotSaveWithEmptyNewPasswordAndNonemptyPassword) { observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillOnce(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillOnce(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1660,7 +1682,7 @@ TEST_F(PasswordManagerTest, DoNotSaveWithEmptyNewPasswordAndNonemptyPassword) { form_manager_to_save->GetPendingCredentials().password_value); } -TEST_F(PasswordManagerTest, FormSubmitWithOnlyPasswordField) { +TEST_P(PasswordManagerTest, FormSubmitWithOnlyPasswordField) { // Test to verify that on submitting the HTML password form without having // username input filed shows password save promt and saves the password to // store. @@ -1673,11 +1695,11 @@ TEST_F(PasswordManagerTest, FormSubmitWithOnlyPasswordField) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -1709,7 +1731,7 @@ TEST_F(PasswordManagerTest, FormSubmitWithOnlyPasswordField) { // rendered by different processes, two unrelated forms can end up with the same // ID. The test checks that nevertheless each of them gets assigned its own // PasswordFormManager and filled as expected. -TEST_F(PasswordManagerTest, FillPasswordOnManyFrames_SameId) { +TEST_P(PasswordManagerTest, FillPasswordOnManyFrames_SameId) { // Setting task runner is required since PasswordFormManager uses // PostDelayTask for making filling. PasswordFormManager::set_wait_for_server_predictions_for_filling(true); @@ -1749,7 +1771,7 @@ TEST_F(PasswordManagerTest, FillPasswordOnManyFrames_SameId) { // Observe the form in the first frame. EXPECT_CALL(*store_, GetLogins(PasswordStore::FormDigest(first_form.form_data), _)) - .WillOnce(WithArg<1>(InvokeConsumer(first_form))); + .WillOnce(WithArg<1>(InvokeConsumer(store_.get(), first_form))); EXPECT_CALL(driver_, FillPasswordForm(_)); manager()->OnPasswordFormsParsed(&driver_, {first_form.form_data}); @@ -1757,22 +1779,22 @@ TEST_F(PasswordManagerTest, FillPasswordOnManyFrames_SameId) { MockPasswordManagerDriver driver_b; EXPECT_CALL(*store_, GetLogins(PasswordStore::FormDigest(second_form.form_data), _)) - .WillOnce(WithArg<1>(InvokeConsumer(second_form))); + .WillOnce(WithArg<1>(InvokeConsumer(store_.get(), second_form))); EXPECT_CALL(driver_b, FillPasswordForm(_)); manager()->OnPasswordFormsParsed(&driver_b, {second_form.form_data}); task_runner_->FastForwardUntilNoTasksRemain(); } -TEST_F(PasswordManagerTest, SameDocumentNavigation) { +TEST_P(PasswordManagerTest, SameDocumentNavigation) { // Test that observing a newly submitted form shows the save password bar on // call in page navigation. std::vector<FormData> observed; PasswordForm form(MakeSimpleForm()); observed.push_back(form.form_data); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); manager()->ShowManualFallbackForSaving(&driver_, form.form_data); @@ -1788,7 +1810,7 @@ TEST_F(PasswordManagerTest, SameDocumentNavigation) { form_manager_to_save->Save(); } -TEST_F(PasswordManagerTest, SameDocumentBlacklistedSite) { +TEST_P(PasswordManagerTest, SameDocumentBlacklistedSite) { // Test that observing a newly submitted form on blacklisted site does notify // the embedder on call in page navigation. std::vector<FormData> observed; @@ -1801,8 +1823,9 @@ TEST_F(PasswordManagerTest, SameDocumentBlacklistedSite) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(blacklisted_form))); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + .WillRepeatedly( + WithArg<1>(InvokeConsumer(store_.get(), blacklisted_form))); + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1816,7 +1839,7 @@ TEST_F(PasswordManagerTest, SameDocumentBlacklistedSite) { EXPECT_TRUE(form_manager_to_save->IsBlacklisted()); } -TEST_F(PasswordManagerTest, FormSubmittedUnchangedNotifiesClient) { +TEST_P(PasswordManagerTest, FormSubmittedUnchangedNotifiesClient) { // This tests verifies that if the observed forms and provisionally saved // forms are the same, then successful submission notifies the client. std::vector<FormData> observed; @@ -1826,11 +1849,11 @@ TEST_F(PasswordManagerTest, FormSubmittedUnchangedNotifiesClient) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -1853,7 +1876,7 @@ TEST_F(PasswordManagerTest, FormSubmittedUnchangedNotifiesClient) { EXPECT_THAT(form, FormMatches(notified_form)); } -TEST_F(PasswordManagerTest, SaveFormFetchedAfterSubmit) { +TEST_P(PasswordManagerTest, SaveFormFetchedAfterSubmit) { // Test that a password is offered for saving even if the response from the // PasswordStore comes after submit. std::vector<FormData> observed; @@ -1867,7 +1890,7 @@ TEST_F(PasswordManagerTest, SaveFormFetchedAfterSubmit) { manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -1875,8 +1898,8 @@ TEST_F(PasswordManagerTest, SaveFormFetchedAfterSubmit) { // Emulate fetching password form from PasswordStore after submission but // before post-navigation load. ASSERT_TRUE(store_consumer); - store_consumer->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + store_consumer->OnGetPasswordStoreResultsFrom( + store_, std::vector<std::unique_ptr<PasswordForm>>()); std::unique_ptr<PasswordFormManagerForUI> form_manager_to_save; EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)) @@ -1893,12 +1916,12 @@ TEST_F(PasswordManagerTest, SaveFormFetchedAfterSubmit) { form_manager_to_save->Save(); } -TEST_F(PasswordManagerTest, PasswordGeneration_FailedSubmission) { +TEST_P(PasswordManagerTest, PasswordGeneration_FailedSubmission) { std::vector<FormData> observed; FormData form_data(MakeFormDataWithOnlyNewPasswordField()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1911,7 +1934,7 @@ TEST_F(PasswordManagerTest, PasswordGeneration_FailedSubmission) { // Do not save generated password when the password form reappears. EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)).Times(0); EXPECT_CALL(*store_, AddLogin(_)).Times(0); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()).Times(0); + EXPECT_CALL(client_, AutomaticPasswordSave).Times(0); // Simulate submission failing, with the same form being visible after // navigation. @@ -1922,12 +1945,12 @@ TEST_F(PasswordManagerTest, PasswordGeneration_FailedSubmission) { // If the user edits the generated password, but does not remove it completely, // it should stay treated as a generated password. -TEST_F(PasswordManagerTest, PasswordGenerationPasswordEdited_FailedSubmission) { +TEST_P(PasswordManagerTest, PasswordGenerationPasswordEdited_FailedSubmission) { std::vector<FormData> observed; FormData form_data(MakeFormDataWithOnlyNewPasswordField()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1945,7 +1968,7 @@ TEST_F(PasswordManagerTest, PasswordGenerationPasswordEdited_FailedSubmission) { // Do not save generated password when the password form reappears. EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)).Times(0); EXPECT_CALL(*store_, AddLogin(_)).Times(0); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()).Times(0); + EXPECT_CALL(client_, AutomaticPasswordSave).Times(0); // Simulate submission failing, with the same form being visible after // navigation. @@ -1956,13 +1979,13 @@ TEST_F(PasswordManagerTest, PasswordGenerationPasswordEdited_FailedSubmission) { // Generated password are saved even if it looks like the submit failed (the // form reappeared). Verify that passwords which are no longer marked as // generated will not be automatically saved. -TEST_F(PasswordManagerTest, +TEST_P(PasswordManagerTest, PasswordGenerationNoLongerGeneratedPasswordNotForceSaved_FailedSubmit) { std::vector<FormData> observed; FormData form_data(MakeFormDataWithOnlyNewPasswordField()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -1981,7 +2004,7 @@ TEST_F(PasswordManagerTest, // No infobar or prompt is shown if submission fails. EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)).Times(0); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()).Times(0); + EXPECT_CALL(client_, AutomaticPasswordSave).Times(0); // Simulate submission failing, with the same form being visible after // navigation. @@ -1991,13 +2014,13 @@ TEST_F(PasswordManagerTest, // Verify that passwords which are no longer generated trigger the confirmation // dialog when submitted. -TEST_F(PasswordManagerTest, +TEST_P(PasswordManagerTest, PasswordGenerationNoLongerGeneratedPasswordNotForceSaved) { std::vector<FormData> observed; FormData form_data(MakeFormDataWithOnlyNewPasswordField()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2018,7 +2041,7 @@ TEST_F(PasswordManagerTest, std::unique_ptr<PasswordFormManagerForUI> form_to_save; EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr(_)) .WillOnce(WithArg<0>(SaveToScopedPtr(&form_to_save))); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()).Times(0); + EXPECT_CALL(client_, AutomaticPasswordSave).Times(0); // Simulate a successful submission. observed.clear(); @@ -2026,12 +2049,12 @@ TEST_F(PasswordManagerTest, manager()->OnPasswordFormsRendered(&driver_, observed, true); } -TEST_F(PasswordManagerTest, PasswordGenerationUsernameChanged) { +TEST_P(PasswordManagerTest, PasswordGenerationUsernameChanged) { std::vector<FormData> observed; FormData form_data(MakeFormDataWithOnlyNewPasswordField()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2050,7 +2073,7 @@ TEST_F(PasswordManagerTest, PasswordGenerationUsernameChanged) { PasswordForm form_to_save; EXPECT_CALL(*store_, UpdateLoginWithPrimaryKey(_, _)) .WillOnce(SaveArg<0>(&form_to_save)); - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()); + EXPECT_CALL(client_, AutomaticPasswordSave); observed.clear(); manager()->OnPasswordFormsParsed(&driver_, observed); @@ -2059,14 +2082,14 @@ TEST_F(PasswordManagerTest, PasswordGenerationUsernameChanged) { EXPECT_EQ(form_data.fields[1].value, form_to_save.password_value); } -TEST_F(PasswordManagerTest, PasswordGenerationPresavePassword) { +TEST_P(PasswordManagerTest, PasswordGenerationPresavePassword) { std::vector<FormData> observed; PasswordForm form(MakeFormWithOnlyNewPasswordField()); observed.push_back(form.form_data); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2099,13 +2122,13 @@ TEST_F(PasswordManagerTest, PasswordGenerationPresavePassword) { manager()->OnPasswordNoLongerGenerated(&driver_, updated_form.form_data); } -TEST_F(PasswordManagerTest, PasswordGenerationPresavePassword_NoFormManager) { +TEST_P(PasswordManagerTest, PasswordGenerationPresavePassword_NoFormManager) { // Checks that GeneratedFormHasNoFormManager metric is sent if there is no // corresponding PasswordFormManager for the given form. It should be uncommon // case. std::vector<FormData> observed; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2122,7 +2145,7 @@ TEST_F(PasswordManagerTest, PasswordGenerationPresavePassword_NoFormManager) { "PasswordManager.GeneratedFormHasNoFormManager", true, 1); } -TEST_F(PasswordManagerTest, PasswordGenerationPresavePasswordAndLogin) { +TEST_P(PasswordManagerTest, PasswordGenerationPresavePasswordAndLogin) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); const bool kFalseTrue[] = {false, true}; @@ -2133,13 +2156,14 @@ TEST_F(PasswordManagerTest, PasswordGenerationPresavePasswordAndLogin) { std::vector<FormData> observed = {form.form_data}; if (found_matched_logins_in_store) { EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(2); } else { EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly( + WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); } - EXPECT_CALL(client_, AutomaticPasswordSaveIndicator()) + EXPECT_CALL(client_, AutomaticPasswordSave) .Times(found_matched_logins_in_store ? 0 : 1); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2184,16 +2208,17 @@ TEST_F(PasswordManagerTest, PasswordGenerationPresavePasswordAndLogin) { } } -TEST_F(PasswordManagerTest, SetGenerationElementAndReasonForForm) { +TEST_P(PasswordManagerTest, SetGenerationElementAndReasonForForm) { PasswordForm form(MakeSimpleForm()); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(PasswordStore::FormDigest(form), _)); manager()->OnPasswordFormsParsed(&driver_, {form.form_data}); - manager()->SetGenerationElementAndReasonForForm(&driver_, form.form_data, - ASCIIToUTF16("psw"), false); + manager()->SetGenerationElementAndReasonForForm( + &driver_, form.form_data, form.form_data.fields[1].unique_renderer_id, + false); EXPECT_CALL(*store_, AddLogin(_)); manager()->OnPresaveGeneratedPassword(&driver_, form.form_data, form.password_value); @@ -2204,12 +2229,12 @@ TEST_F(PasswordManagerTest, SetGenerationElementAndReasonForForm) { EXPECT_TRUE(form_manager->HasGeneratedPassword()); } -TEST_F(PasswordManagerTest, UpdateFormManagers) { +TEST_P(PasswordManagerTest, UpdateFormManagers) { // Seeing a form should result in creating PasswordFormManager and // PasswordFormManager and querying PasswordStore. Calling // UpdateFormManagers should result in querying the store again. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {PasswordForm().form_data}); @@ -2217,7 +2242,7 @@ TEST_F(PasswordManagerTest, UpdateFormManagers) { manager()->UpdateFormManagers(); } -TEST_F(PasswordManagerTest, AutofillingOfAffiliatedCredentials) { +TEST_P(PasswordManagerTest, AutofillingOfAffiliatedCredentials) { PasswordForm android_form(MakeAndroidCredential()); PasswordForm observed_form(MakeSimpleForm()); std::vector<FormData> observed_forms; @@ -2228,7 +2253,7 @@ TEST_F(PasswordManagerTest, AutofillingOfAffiliatedCredentials) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(android_form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), android_form))); manager()->OnPasswordFormsParsed(&driver_, observed_forms); observed_forms.clear(); manager()->OnPasswordFormsRendered(&driver_, observed_forms, true); @@ -2238,7 +2263,7 @@ TEST_F(PasswordManagerTest, AutofillingOfAffiliatedCredentials) { EXPECT_FALSE(form_data.wait_for_username); EXPECT_EQ(android_form.signon_realm, form_data.preferred_realm); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(observed_form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(observed_form.url)) .WillRepeatedly(Return(true)); PasswordForm filled_form(observed_form); @@ -2270,7 +2295,7 @@ TEST_F(PasswordManagerTest, AutofillingOfAffiliatedCredentials) { // If the manager fills a credential originally saved from an affiliated Android // application, and the user overwrites the password, they should be prompted if // they want to update. If so, the Android credential itself should be updated. -TEST_F(PasswordManagerTest, UpdatePasswordOfAffiliatedCredential) { +TEST_P(PasswordManagerTest, UpdatePasswordOfAffiliatedCredential) { PasswordForm android_form(MakeAndroidCredential()); PasswordForm observed_form(MakeSimpleForm()); std::vector<FormData> observed_forms = {observed_form.form_data}; @@ -2279,11 +2304,11 @@ TEST_F(PasswordManagerTest, UpdatePasswordOfAffiliatedCredential) { // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(android_form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), android_form))); manager()->OnPasswordFormsParsed(&driver_, observed_forms); manager()->OnPasswordFormsRendered(&driver_, observed_forms, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(observed_form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(observed_form.url)) .WillRepeatedly(Return(true)); PasswordForm filled_form(observed_form); @@ -2314,7 +2339,7 @@ TEST_F(PasswordManagerTest, UpdatePasswordOfAffiliatedCredential) { EXPECT_THAT(saved_form, FormMatches(expected_form)); } -TEST_F(PasswordManagerTest, ClearedFieldsSuccessCriteria) { +TEST_P(PasswordManagerTest, ClearedFieldsSuccessCriteria) { // Test that a submission is considered to be successful on a change password // form without username when fields valued are cleared. PasswordForm form(MakeFormWithOnlyNewPasswordField()); @@ -2325,10 +2350,10 @@ TEST_F(PasswordManagerTest, ClearedFieldsSuccessCriteria) { // Emulate page load. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); OnPasswordFormSubmitted(form.form_data); @@ -2349,7 +2374,7 @@ TEST_F(PasswordManagerTest, ClearedFieldsSuccessCriteria) { // Check that no sync password hash is saved when no username is available, // because we it's not clear whether the submitted credentials are sync // credentials. -TEST_F(PasswordManagerTest, NotSavingSyncPasswordHash_NoUsername) { +TEST_P(PasswordManagerTest, NotSavingSyncPasswordHash_NoUsername) { // Simulate loading a simple form with no existing stored password. std::vector<FormData> observed; FormData form_data(MakeSimpleGAIAFormData()); @@ -2357,7 +2382,7 @@ TEST_F(PasswordManagerTest, NotSavingSyncPasswordHash_NoUsername) { form_data.fields[0].value.clear(); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsRendered(&driver_, observed, true); EXPECT_CALL(client_, IsSavingAndFillingEnabled(form_data.url)) @@ -2375,12 +2400,12 @@ TEST_F(PasswordManagerTest, NotSavingSyncPasswordHash_NoUsername) { // Check that no sync password hash is saved when the submitted credentials are // not qualified as sync credentials. -TEST_F(PasswordManagerTest, NotSavingSyncPasswordHash_NotSyncCredentials) { +TEST_P(PasswordManagerTest, NotSavingSyncPasswordHash_NotSyncCredentials) { // Simulate loading a simple form with no existing stored password. FormData form_data(MakeSimpleGAIAFormData()); std::vector<FormData> observed = {form_data}; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsRendered(&driver_, observed, true); EXPECT_CALL(client_, IsSavingAndFillingEnabled(form_data.url)) @@ -2400,7 +2425,7 @@ TEST_F(PasswordManagerTest, NotSavingSyncPasswordHash_NotSyncCredentials) { } #endif -TEST_F(PasswordManagerTest, ManualFallbackForSaving) { +TEST_P(PasswordManagerTest, ManualFallbackForSaving) { ukm::TestAutoSetUkmRecorder test_ukm_recorder; std::vector<FormData> observed; @@ -2408,12 +2433,12 @@ TEST_F(PasswordManagerTest, ManualFallbackForSaving) { observed.push_back(form.form_data); PasswordForm stored_form = form; stored_form.password_value = ASCIIToUTF16("old_password"); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(stored_form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), stored_form))); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(2); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2456,7 +2481,7 @@ TEST_F(PasswordManagerTest, ManualFallbackForSaving) { // Tests that the manual fallback for saving isn't shown if there is no response // from the password storage. When crbug.com/741537 is fixed, change this test. -TEST_F(PasswordManagerTest, ManualFallbackForSaving_SlowBackend) { +TEST_P(PasswordManagerTest, ManualFallbackForSaving_SlowBackend) { std::vector<FormData> observed; FormData form_data(MakeSimpleFormData()); observed.push_back(form_data); @@ -2476,24 +2501,24 @@ TEST_F(PasswordManagerTest, ManualFallbackForSaving_SlowBackend) { // The storage responded. The fallback can be shown. ASSERT_TRUE(store_consumer); - store_consumer->OnGetPasswordStoreResults( - std::vector<std::unique_ptr<PasswordForm>>()); + store_consumer->OnGetPasswordStoreResultsFrom( + store_, std::vector<std::unique_ptr<PasswordForm>>()); std::unique_ptr<PasswordFormManagerForUI> form_manager_to_save; EXPECT_CALL(client_, ShowManualFallbackForSavingPtr(_, false, false)) .WillOnce(WithArg<0>(SaveToScopedPtr(&form_manager_to_save))); manager()->ShowManualFallbackForSaving(&driver_, form_data); } -TEST_F(PasswordManagerTest, ManualFallbackForSaving_GeneratedPassword) { +TEST_P(PasswordManagerTest, ManualFallbackForSaving_GeneratedPassword) { std::vector<FormData> observed; PasswordForm form(MakeSimpleForm()); observed.push_back(form.form_data); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); // TODO(https://crbug.com/949519): replace WillRepeatedly with WillOnce when // the old parser is gone. EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2527,10 +2552,10 @@ TEST_F(PasswordManagerTest, ManualFallbackForSaving_GeneratedPassword) { } // Sync password hash should be updated upon submission of change password page. -TEST_F(PasswordManagerTest, SaveSyncPasswordHashOnChangePasswordPage) { +TEST_P(PasswordManagerTest, SaveSyncPasswordHashOnChangePasswordPage) { FormData form_data(MakeGAIAChangePasswordFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); std::vector<FormData> observed; observed.push_back(form_data); @@ -2563,10 +2588,10 @@ TEST_F(PasswordManagerTest, SaveSyncPasswordHashOnChangePasswordPage) { #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) // Non-Sync Gaia password hash should be saved upon submission of Gaia login // page. -TEST_F(PasswordManagerTest, SaveOtherGaiaPasswordHash) { +TEST_P(PasswordManagerTest, SaveOtherGaiaPasswordHash) { FormData form_data(MakeSimpleGAIAFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); std::vector<FormData> observed; observed.push_back(form_data); @@ -2593,10 +2618,10 @@ TEST_F(PasswordManagerTest, SaveOtherGaiaPasswordHash) { // Non-Sync Gaia password hash should be saved upon submission of change // password page. -TEST_F(PasswordManagerTest, SaveOtherGaiaPasswordHashOnChangePasswordPage) { +TEST_P(PasswordManagerTest, SaveOtherGaiaPasswordHashOnChangePasswordPage) { FormData form_data(MakeGAIAChangePasswordFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); std::vector<FormData> observed; observed.push_back(form_data); @@ -2624,10 +2649,10 @@ TEST_F(PasswordManagerTest, SaveOtherGaiaPasswordHashOnChangePasswordPage) { // Enterprise password hash should be saved upon submission of enterprise login // page. -TEST_F(PasswordManagerTest, SaveEnterprisePasswordHash) { +TEST_P(PasswordManagerTest, SaveEnterprisePasswordHash) { FormData form_data(MakeSimpleFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); std::vector<FormData> observed; observed.push_back(form_data); @@ -2653,12 +2678,12 @@ TEST_F(PasswordManagerTest, SaveEnterprisePasswordHash) { #endif // If there are no forms to parse, certificate errors should not be reported. -TEST_F(PasswordManagerTest, CertErrorReported_NoForms) { +TEST_P(PasswordManagerTest, CertErrorReported_NoForms) { const std::vector<FormData> observed; EXPECT_CALL(client_, GetMainFrameCertStatus()) .WillRepeatedly(Return(net::CERT_STATUS_AUTHORITY_INVALID)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); base::HistogramTester histogram_tester; manager()->OnPasswordFormsParsed(&driver_, observed); @@ -2666,7 +2691,7 @@ TEST_F(PasswordManagerTest, CertErrorReported_NoForms) { "PasswordManager.CertificateErrorsWhileSeeingForms", 0); } -TEST_F(PasswordManagerTest, CertErrorReported) { +TEST_P(PasswordManagerTest, CertErrorReported) { constexpr struct { net::CertStatus cert_status; metrics_util::CertificateError expected_error; @@ -2708,17 +2733,16 @@ TEST_F(PasswordManagerTest, CertErrorReported) { } } -TEST_F(PasswordManagerTest, CreatingFormManagers) { +TEST_P(PasswordManagerTest, CreatingFormManagers) { FormData form_data(MakeSimpleFormData()); std::vector<FormData> observed; observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); // Check that the form manager is created. EXPECT_EQ(1u, manager()->form_managers().size()); - EXPECT_TRUE(manager()->form_managers()[0]->DoesManage(form_data, - client_.GetDriver())); + EXPECT_TRUE(manager()->form_managers()[0]->DoesManage(form_data, &driver_)); // Check that receiving the same form the second time does not lead to // creating new form manager. @@ -2729,7 +2753,7 @@ TEST_F(PasswordManagerTest, CreatingFormManagers) { // Tests that processing normal HTML form submissions works properly with the // new parsing. For details see scheme 1 in comments before // |form_managers_| in password_manager.h. -TEST_F(PasswordManagerTest, ProcessingNormalFormSubmission) { +TEST_P(PasswordManagerTest, ProcessingNormalFormSubmission) { for (bool successful_submission : {false, true}) { SCOPED_TRACE(testing::Message("successful_submission = ") << successful_submission); @@ -2739,7 +2763,7 @@ TEST_F(PasswordManagerTest, ProcessingNormalFormSubmission) { FormData form_data(MakeSimpleFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); std::vector<FormData> observed; observed.push_back(form_data); @@ -2774,13 +2798,13 @@ TEST_F(PasswordManagerTest, ProcessingNormalFormSubmission) { // Tests that processing form submissions without navigations works properly // with the new parsing. For details see scheme 2 in comments before // |form_managers_| in password_manager.h. -TEST_F(PasswordManagerTest, ProcessingOtherSubmissionTypes) { +TEST_P(PasswordManagerTest, ProcessingOtherSubmissionTypes) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); FormData form_data(MakeSimpleFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); std::vector<FormData> observed; observed.push_back(form_data); @@ -2800,14 +2824,14 @@ TEST_F(PasswordManagerTest, ProcessingOtherSubmissionTypes) { EXPECT_TRUE(manager()->form_managers().empty()); } -TEST_F(PasswordManagerTest, SubmittedGaiaFormWithoutVisiblePasswordField) { +TEST_P(PasswordManagerTest, SubmittedGaiaFormWithoutVisiblePasswordField) { // Tests that a submitted GAIA sign-in form which does not contain a visible // password field is skipped. std::vector<FormData> observed; FormData form_data(MakeSimpleGAIAFormData()); observed.push_back(form_data); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2823,7 +2847,7 @@ TEST_F(PasswordManagerTest, SubmittedGaiaFormWithoutVisiblePasswordField) { form_data.submission_event); } -TEST_F(PasswordManagerTest, MetricForSchemeOfSuccessfulLogins) { +TEST_P(PasswordManagerTest, MetricForSchemeOfSuccessfulLogins) { for (bool origin_is_secure : {false, true}) { SCOPED_TRACE(testing::Message("origin_is_secure = ") << origin_is_secure); FormData form_data(MakeSimpleFormData()); @@ -2831,7 +2855,7 @@ TEST_F(PasswordManagerTest, MetricForSchemeOfSuccessfulLogins) { GURL(origin_is_secure ? "https://example.com" : "http://example.com"); std::vector<FormData> observed = {form_data}; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2852,7 +2876,7 @@ TEST_F(PasswordManagerTest, MetricForSchemeOfSuccessfulLogins) { } } -TEST_F(PasswordManagerTest, ManualFallbackForSavingNewParser) { +TEST_P(PasswordManagerTest, ManualFallbackForSavingNewParser) { PasswordFormManager::set_wait_for_server_predictions_for_filling(false); std::vector<FormData> observed; @@ -2860,10 +2884,10 @@ TEST_F(PasswordManagerTest, ManualFallbackForSavingNewParser) { observed.push_back(form.form_data); PasswordForm stored_form = form; stored_form.password_value = ASCIIToUTF16("old_password"); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(stored_form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), stored_form))); EXPECT_CALL(driver_, FillPasswordForm(_)).Times(2); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -2892,12 +2916,12 @@ TEST_F(PasswordManagerTest, ManualFallbackForSavingNewParser) { manager()->HideManualFallbackForSaving(); } -TEST_F(PasswordManagerTest, NoSavePromptWhenPasswordManagerDisabled) { +TEST_P(PasswordManagerTest, NoSavePromptWhenPasswordManagerDisabled) { FormData form_data(MakeSimpleFormData()); EXPECT_CALL(client_, IsSavingAndFillingEnabled(form_data.url)) .WillRepeatedly(Return(false)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form_data}); @@ -2910,12 +2934,12 @@ TEST_F(PasswordManagerTest, NoSavePromptWhenPasswordManagerDisabled) { &driver_, submitted_form_data.submission_event); } -TEST_F(PasswordManagerTest, NoSavePromptForNotPasswordForm) { +TEST_P(PasswordManagerTest, NoSavePromptForNotPasswordForm) { FormData form_data(MakeSimpleFormData()); EXPECT_CALL(client_, IsSavingAndFillingEnabled(form_data.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); // Make the form to be credit card form. form_data.fields[1].autocomplete_attribute = "cc-csc"; @@ -2932,7 +2956,7 @@ TEST_F(PasswordManagerTest, NoSavePromptForNotPasswordForm) { // Check that when autofill predictions are received before a form is found then // server predictions are not ignored and used for filling. -TEST_F(PasswordManagerTest, AutofillPredictionBeforeFormParsed) { +TEST_P(PasswordManagerTest, AutofillPredictionBeforeFormParsed) { PasswordFormManager::set_wait_for_server_predictions_for_filling(true); EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); @@ -2950,7 +2974,7 @@ TEST_F(PasswordManagerTest, AutofillPredictionBeforeFormParsed) { #endif EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form))); EXPECT_CALL(driver_, FillPasswordForm(_)); // Simulate that the form is incorrectly marked as sign-up, which means it can @@ -2963,7 +2987,7 @@ TEST_F(PasswordManagerTest, AutofillPredictionBeforeFormParsed) { // Check that when autofill predictions are received before a form is found then // server predictions are not ignored and used for filling in case there are // multiple forms on a page, including forms that have UsernameFirstFlow votes. -TEST_F(PasswordManagerTest, AutofillPredictionBeforeMultipleFormsParsed) { +TEST_P(PasswordManagerTest, AutofillPredictionBeforeMultipleFormsParsed) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature(features::kUsernameFirstFlow); PasswordFormManager::set_wait_for_server_predictions_for_filling(true); @@ -2974,7 +2998,7 @@ TEST_F(PasswordManagerTest, AutofillPredictionBeforeMultipleFormsParsed) { PasswordForm form2(MakeSimpleForm()); EXPECT_CALL(*store_, GetLogins) - .WillRepeatedly(WithArg<1>(InvokeConsumer(form2))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), form2))); FormStructure form_structure1(form1.form_data); form_structure1.field(0)->set_server_type(autofill::SINGLE_USERNAME); @@ -3008,15 +3032,15 @@ TEST_F(PasswordManagerTest, AutofillPredictionBeforeMultipleFormsParsed) { // 2. Navigation happens. // 3. The password disappeared after navigation. // 4. A save prompt is shown. -TEST_F(PasswordManagerTest, SavingAfterUserTypingAndNavigation) { +TEST_P(PasswordManagerTest, SavingAfterUserTypingAndNavigation) { for (bool form_may_be_submitted : {false, true}) { SCOPED_TRACE(testing::Message() << "form_may_be_submitted = " << form_may_be_submitted); PasswordForm form(MakeSimpleForm()); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form.form_data}); // The user is typing as a result the saving manual fallback is shown. @@ -3048,17 +3072,17 @@ TEST_F(PasswordManagerTest, SavingAfterUserTypingAndNavigation) { // Check that when a form is submitted and a PasswordFormManager not present, // this ends up reported in ProvisionallySaveFailure UMA and UKM. -TEST_F(PasswordManagerTest, ProvisionallySaveFailure) { +TEST_P(PasswordManagerTest, ProvisionallySaveFailure) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(nullptr, {}); base::HistogramTester histogram_tester; ukm::TestAutoSetUkmRecorder test_ukm_recorder; - auto metrics_recorder = std::make_unique<PasswordManagerMetricsRecorder>( - 1234, GURL("http://example.com"), nullptr); + auto metrics_recorder = + std::make_unique<PasswordManagerMetricsRecorder>(1234, nullptr); EXPECT_CALL(client_, GetMetricsRecorder()) .WillRepeatedly(Return(metrics_recorder.get())); @@ -3105,7 +3129,7 @@ struct MissingFormManagerTestCase { // Test that presence of form managers in various situations is appropriately // reported through UKM. -TEST_F(PasswordManagerTest, ReportMissingFormManager) { +TEST_P(PasswordManagerTest, ReportMissingFormManager) { const FormData form_data = MakeSimpleFormData(); FormData other_form_data = MakeSimpleFormData(); other_form_data.unique_renderer_id.value() += 1; @@ -3180,7 +3204,7 @@ TEST_F(PasswordManagerTest, ReportMissingFormManager) { }; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); for (const MissingFormManagerTestCase& test_case : kTestCases) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(test_case.saving == @@ -3191,8 +3215,8 @@ TEST_F(PasswordManagerTest, ReportMissingFormManager) { manager()->OnPasswordFormsParsed(nullptr, test_case.parsed_forms_data); ukm::TestAutoSetUkmRecorder test_ukm_recorder; - auto metrics_recorder = std::make_unique<PasswordManagerMetricsRecorder>( - 1234, GURL("http://example.com"), nullptr); + auto metrics_recorder = + std::make_unique<PasswordManagerMetricsRecorder>(1234, nullptr); EXPECT_CALL(client_, GetMetricsRecorder()) .WillRepeatedly(Return(metrics_recorder.get())); @@ -3228,13 +3252,13 @@ TEST_F(PasswordManagerTest, ReportMissingFormManager) { // Tests that despite there a form was not seen on a page load, new // |PasswordFormManager| is created in process of saving. -TEST_F(PasswordManagerTest, CreatePasswordFormManagerOnSaving) { +TEST_P(PasswordManagerTest, CreatePasswordFormManagerOnSaving) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); PasswordForm form(MakeSimpleForm()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form.form_data}); @@ -3263,13 +3287,13 @@ TEST_F(PasswordManagerTest, CreatePasswordFormManagerOnSaving) { // Tests that no save prompt from form manager is shown when Credentials // Management API function store is called. -TEST_F(PasswordManagerTest, NoSavePromptAfterStoreCalled) { +TEST_P(PasswordManagerTest, NoSavePromptAfterStoreCalled) { EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); FormData form_data(MakeSimpleFormData()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form_data}); @@ -3285,7 +3309,7 @@ TEST_F(PasswordManagerTest, NoSavePromptAfterStoreCalled) { // Check that on non-password form, saving and filling fallbacks are available // but no automatic filling and saving are available. -TEST_F(PasswordManagerTest, FillingAndSavingFallbacksOnNonPasswordForm) { +TEST_P(PasswordManagerTest, FillingAndSavingFallbacksOnNonPasswordForm) { PasswordFormManager::set_wait_for_server_predictions_for_filling(false); EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); @@ -3295,7 +3319,7 @@ TEST_F(PasswordManagerTest, FillingAndSavingFallbacksOnNonPasswordForm) { credit_card_form.only_for_fallback = true; EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(saved_match))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), saved_match))); PasswordFormFillData form_data; EXPECT_CALL(driver_, FillPasswordForm(_)).WillOnce(SaveArg<0>(&form_data)); @@ -3326,7 +3350,7 @@ TEST_F(PasswordManagerTest, FillingAndSavingFallbacksOnNonPasswordForm) { #if !defined(OS_IOS) // Check that on successful login the credentials are checked for leak. -TEST_F(PasswordManagerTest, StartLeakDetection) { +TEST_P(PasswordManagerTest, StartLeakDetection) { auto mock_factory = std::make_unique<testing::StrictMock<MockLeakDetectionCheckFactory>>(); MockLeakDetectionCheckFactory* weak_factory = mock_factory.get(); @@ -3335,7 +3359,7 @@ TEST_F(PasswordManagerTest, StartLeakDetection) { const FormData form_data = MakeSimpleFormData(); std::vector<FormData> observed = {form_data}; EXPECT_CALL(*store_, GetLogins) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, observed); manager()->OnPasswordFormsRendered(&driver_, observed, true); @@ -3359,7 +3383,7 @@ TEST_F(PasswordManagerTest, StartLeakDetection) { #endif // !defined(OS_IOS) // Check that a non-password form with SINGLE_USERNAME prediction is filled. -TEST_F(PasswordManagerTest, FillSingleUsername) { +TEST_P(PasswordManagerTest, FillSingleUsername) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature(features::kUsernameFirstFlow); PasswordFormManager::set_wait_for_server_predictions_for_filling(true); @@ -3367,9 +3391,9 @@ TEST_F(PasswordManagerTest, FillSingleUsername) { .WillRepeatedly(Return(true)); PasswordForm saved_match(MakeSavedForm()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(saved_match))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), saved_match))); - // Create FormdData for a form with 1 text field. + // Create FormData for a form with 1 text field. FormData form_data; constexpr FormRendererId form_id(1001); form_data.unique_renderer_id = form_id; @@ -3400,7 +3424,7 @@ TEST_F(PasswordManagerTest, FillSingleUsername) { // Checks that a password form with a clear-text account creation field results // in marking the password field as eligible for password generation. -TEST_F(PasswordManagerTest, +TEST_P(PasswordManagerTest, MarkServerPredictedClearTextPasswordFieldEligibleForGeneration) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature( @@ -3411,7 +3435,7 @@ TEST_F(PasswordManagerTest, .WillRepeatedly(Return(true)); PasswordForm saved_match(MakeSavedForm()); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(saved_match))); + .WillRepeatedly(WithArg<1>(InvokeConsumer(store_.get(), saved_match))); // Create FormdData for a form with 1 text field. FormData form_data; @@ -3445,16 +3469,16 @@ TEST_F(PasswordManagerTest, } // Checks that username is saved on username first flow. -TEST_F(PasswordManagerTest, UsernameFirstFlow) { +TEST_P(PasswordManagerTest, UsernameFirstFlow) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature(features::kUsernameFirstFlow); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); PasswordForm form(MakeSimpleFormWithOnlyPasswordField()); // Simulate the user typed a username in username form. const base::string16 username = ASCIIToUTF16("username1"); - EXPECT_CALL(driver_, GetLastCommittedURL()).WillOnce(ReturnRef(form.origin)); + EXPECT_CALL(driver_, GetLastCommittedURL()).WillOnce(ReturnRef(form.url)); manager()->OnUserModifiedNonPasswordField(&driver_, FieldRendererId(1001), username /* value */); @@ -3462,7 +3486,7 @@ TEST_F(PasswordManagerTest, UsernameFirstFlow) { // to the page. manager()->OnPasswordFormsParsed(&driver_, {form.form_data} /* observed */); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); // Simulate that the user typed password and submitted the password form. @@ -3497,11 +3521,12 @@ TEST_F(PasswordManagerTest, UsernameFirstFlow) { #if !defined(OS_IOS) // Checks that username is filled on username first flow based on server and // local predictions. -TEST_F(PasswordManagerTest, UsernameFirstFlowFillingServerAndLocalPredictions) { +TEST_P(PasswordManagerTest, UsernameFirstFlowFillingServerAndLocalPredictions) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature(features::kUsernameFirstFlow); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeConsumer(MakeSavedForm()))); + .WillRepeatedly( + WithArg<1>(InvokeConsumer(store_.get(), MakeSavedForm()))); EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true)); constexpr FieldRendererId kFieldRendererId(1); @@ -3545,10 +3570,10 @@ TEST_F(PasswordManagerTest, UsernameFirstFlowFillingServerAndLocalPredictions) { } #endif -TEST_F(PasswordManagerTest, FormSubmittedOnMainFrame) { +TEST_P(PasswordManagerTest, FormSubmittedOnMainFrame) { EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); FormData form_data(MakeSimpleFormData()); // Submit |form| on a main frame. @@ -3569,10 +3594,10 @@ TEST_F(PasswordManagerTest, FormSubmittedOnMainFrame) { true /* did stop loading */); } -TEST_F(PasswordManagerTest, FormSubmittedOnIFrame) { +TEST_P(PasswordManagerTest, FormSubmittedOnIFrame) { EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); FormData form_data(MakeSimpleFormData()); // Submit |form| on an iframe. @@ -3599,10 +3624,10 @@ TEST_F(PasswordManagerTest, FormSubmittedOnIFrame) { true /* did stop loading */); } -TEST_F(PasswordManagerTest, FormSubmittedOnIFrameMainFrameLoaded) { +TEST_P(PasswordManagerTest, FormSubmittedOnIFrameMainFrameLoaded) { EXPECT_CALL(client_, IsSavingAndFillingEnabled).WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins(_, _)) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); FormData form_data(MakeSimpleFormData()); // Simulate a form submission on an iframe. @@ -3618,23 +3643,27 @@ TEST_F(PasswordManagerTest, FormSubmittedOnIFrameMainFrameLoaded) { true /* did stop loading */); } -TEST_F(PasswordManagerTest, NoPromptAutofillAssistantManuallyCuratedScript) { +TEST_P(PasswordManagerTest, NoPromptAutofillAssistantManuallyCuratedScript) { manager()->SetAutofillAssistantMode(AutofillAssistantMode::kRunning); - PasswordForm form(MakeSimpleForm()); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(_)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); - manager()->OnPasswordFormsParsed(&driver_, {form.form_data}); - manager()->ShowManualFallbackForSaving(&driver_, form.form_data); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); // Check that a save prompt is not shown. EXPECT_CALL(client_, PromptUserToSaveOrUpdatePasswordPtr).Times(0); - manager()->DidNavigateMainFrame(true /* form_may_be_submitted */); - manager()->OnPasswordFormsRendered(&driver_, {} /* observed */, - true /* did stop loading */); + // Simulate multiple submissions. + for (size_t i = 0; i < 2; i++) { + PasswordForm form(MakeSimpleForm()); + manager()->OnPasswordFormsParsed(&driver_, {form.form_data}); + manager()->ShowManualFallbackForSaving(&driver_, form.form_data); + + manager()->DidNavigateMainFrame(true /* form_may_be_submitted */); + manager()->OnPasswordFormsRendered(&driver_, {} /* observed */, + true /* did stop loading */); + } } // Tests the following scenario: @@ -3643,16 +3672,16 @@ TEST_F(PasswordManagerTest, NoPromptAutofillAssistantManuallyCuratedScript) { // 2. The timeout for prompts disabling expires. // 3. The timer re-enables the prompts. // 4. A prompt is shown after a form submission. -TEST_F(PasswordManagerTest, ResetAutofillAssistantModeAfterTimeout) { +TEST_P(PasswordManagerTest, ResetAutofillAssistantModeAfterTimeout) { manager()->SetDisablePromptsTimeoutToZero(); manager()->SetAutofillAssistantMode(AutofillAssistantMode::kRunning); PasswordForm form(MakeSimpleForm()); - EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.origin)) + EXPECT_CALL(client_, IsSavingAndFillingEnabled(form.url)) .WillRepeatedly(Return(true)); EXPECT_CALL(*store_, GetLogins) - .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms())); + .WillRepeatedly(WithArg<1>(InvokeEmptyConsumerWithForms(store_.get()))); manager()->OnPasswordFormsParsed(&driver_, {form.form_data}); manager()->ShowManualFallbackForSaving(&driver_, form.form_data); @@ -3667,4 +3696,6 @@ TEST_F(PasswordManagerTest, ResetAutofillAssistantModeAfterTimeout) { true /* did stop loading */); } +INSTANTIATE_TEST_SUITE_P(, PasswordManagerTest, testing::Bool()); + } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_manager_util.cc b/chromium/components/password_manager/core/browser/password_manager_util.cc index 3885c44c51c..7f8ed4e5473 100644 --- a/chromium/components/password_manager/core/browser/password_manager_util.cc +++ b/chromium/components/password_manager/core/browser/password_manager_util.cc @@ -34,6 +34,7 @@ #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/signin/public/base/signin_metrics.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "components/sync/driver/sync_service.h" #include "components/sync/driver/sync_user_settings.h" @@ -154,17 +155,20 @@ void UserTriggeredManualGenerationFromContextMenu( } // The client ensures the callback won't be run if it is destroyed, so // base::Unretained is safe. - password_manager_client->TriggerReauthForPrimaryAccount(base::BindOnce( - [](password_manager::PasswordManagerClient* client, - password_manager::PasswordManagerClient::ReauthSucceeded succeeded) { - if (succeeded) { - client->GeneratePassword(); - LogPasswordGenerationEvent( - autofill::password_generation:: - PASSWORD_GENERATION_CONTEXT_MENU_PRESSED); - } - }, - base::Unretained(password_manager_client))); + password_manager_client->TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint::kGeneratePasswordContextMenu, + base::BindOnce( + [](password_manager::PasswordManagerClient* client, + password_manager::PasswordManagerClient::ReauthSucceeded + succeeded) { + if (succeeded) { + client->GeneratePassword(); + LogPasswordGenerationEvent( + autofill::password_generation:: + PASSWORD_GENERATION_CONTEXT_MENU_PRESSED); + } + }, + base::Unretained(password_manager_client))); } // TODO(http://crbug.com/890318): Add unitests to check cleaners are correctly @@ -204,7 +208,7 @@ base::StringPiece GetSignonRealmWithProtocolExcluded(const PasswordForm& form) { // Find the web origin (with protocol excluded) in the signon_realm. const size_t after_protocol = - signon_realm_protocol_excluded.find(form.origin.host_piece()); + signon_realm_protocol_excluded.find(form.url.host_piece()); DCHECK_NE(after_protocol, base::StringPiece::npos); // Keep the string starting with position |after_protocol|. @@ -323,16 +327,15 @@ autofill::PasswordForm MakeNormalizedBlacklistedForm( result.signon_realm = std::move(digest.signon_realm); // In case |digest| corresponds to an Android credential copy the origin as // is, otherwise clear out the path by calling GetOrigin(). - if (password_manager::FacetURI::FromPotentiallyInvalidSpec( - digest.origin.spec()) + if (password_manager::FacetURI::FromPotentiallyInvalidSpec(digest.url.spec()) .IsValidAndroidFacetURI()) { - result.origin = std::move(digest.origin); + result.url = std::move(digest.url); } else { // GetOrigin() will return an empty GURL if the origin is not valid or // standard. DCHECK that this will not happen. - DCHECK(digest.origin.is_valid()); - DCHECK(digest.origin.IsStandard()); - result.origin = digest.origin.GetOrigin(); + DCHECK(digest.url.is_valid()); + DCHECK(digest.url.IsStandard()); + result.url = digest.url.GetOrigin(); } return result; } diff --git a/chromium/components/password_manager/core/browser/password_manager_util_unittest.cc b/chromium/components/password_manager/core/browser/password_manager_util_unittest.cc index 764108c41ab..80af63b7227 100644 --- a/chromium/components/password_manager/core/browser/password_manager_util_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_manager_util_unittest.cc @@ -20,6 +20,7 @@ #include "components/password_manager/core/browser/stub_password_manager_client.h" #include "components/password_manager/core/browser/test_password_store.h" #include "components/password_manager/core/common/password_manager_features.h" +#include "components/signin/public/base/signin_metrics.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -45,7 +46,8 @@ class MockPasswordManagerClient MOCK_METHOD(void, TriggerReauthForPrimaryAccount, - (base::OnceCallback<void( + (signin_metrics::ReauthAccessPoint, + base::OnceCallback<void( password_manager::PasswordManagerClient::ReauthSucceeded)>), (override)); MOCK_METHOD(void, GeneratePassword, (), (override)); @@ -54,7 +56,7 @@ class MockPasswordManagerClient autofill::PasswordForm GetTestAndroidCredential() { autofill::PasswordForm form; form.scheme = autofill::PasswordForm::Scheme::kHtml; - form.origin = GURL(kTestAndroidRealm); + form.url = GURL(kTestAndroidRealm); form.signon_realm = kTestAndroidRealm; form.username_value = base::ASCIIToUTF16(kTestUsername); form.password_value = base::ASCIIToUTF16(kTestPassword); @@ -64,8 +66,8 @@ autofill::PasswordForm GetTestAndroidCredential() { autofill::PasswordForm GetTestCredential() { autofill::PasswordForm form; form.scheme = autofill::PasswordForm::Scheme::kHtml; - form.origin = GURL(kTestURL); - form.signon_realm = form.origin.GetOrigin().spec(); + form.url = GURL(kTestURL); + form.signon_realm = form.url.GetOrigin().spec(); form.username_value = base::ASCIIToUTF16(kTestUsername); form.password_value = base::ASCIIToUTF16(kTestPassword); return form; @@ -74,7 +76,7 @@ autofill::PasswordForm GetTestCredential() { autofill::PasswordForm GetTestProxyCredential() { autofill::PasswordForm form; form.scheme = autofill::PasswordForm::Scheme::kBasic; - form.origin = GURL(kTestProxyOrigin); + form.url = GURL(kTestProxyOrigin); form.signon_realm = kTestProxySignonRealm; form.username_value = base::ASCIIToUTF16(kTestUsername); form.password_value = base::ASCIIToUTF16(kTestPassword); @@ -117,17 +119,17 @@ TEST(PasswordManagerUtil, TrimUsernameOnlyCredentials) { TEST(PasswordManagerUtil, GetSignonRealmWithProtocolExcluded) { autofill::PasswordForm http_form; - http_form.origin = GURL("http://www.google.com/page-1/"); + http_form.url = GURL("http://www.google.com/page-1/"); http_form.signon_realm = "http://www.google.com/"; EXPECT_EQ(GetSignonRealmWithProtocolExcluded(http_form), "www.google.com/"); autofill::PasswordForm https_form; - https_form.origin = GURL("https://www.google.com/page-1/"); + https_form.url = GURL("https://www.google.com/page-1/"); https_form.signon_realm = "https://www.google.com/"; EXPECT_EQ(GetSignonRealmWithProtocolExcluded(https_form), "www.google.com/"); autofill::PasswordForm federated_form; - federated_form.origin = GURL("http://localhost:8000/"); + federated_form.url = GURL("http://localhost:8000/"); federated_form.signon_realm = "federation://localhost/accounts.federation.com"; EXPECT_EQ(GetSignonRealmWithProtocolExcluded(federated_form), @@ -409,7 +411,7 @@ TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Android) { EXPECT_TRUE(blacklisted_credential.blacklisted_by_user); EXPECT_EQ(PasswordForm::Scheme::kHtml, blacklisted_credential.scheme); EXPECT_EQ(kTestAndroidRealm, blacklisted_credential.signon_realm); - EXPECT_EQ(GURL(kTestAndroidRealm), blacklisted_credential.origin); + EXPECT_EQ(GURL(kTestAndroidRealm), blacklisted_credential.url); } TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Html) { @@ -419,7 +421,7 @@ TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Html) { EXPECT_EQ(PasswordForm::Scheme::kHtml, blacklisted_credential.scheme); EXPECT_EQ(GURL(kTestURL).GetOrigin().spec(), blacklisted_credential.signon_realm); - EXPECT_EQ(GURL(kTestURL).GetOrigin(), blacklisted_credential.origin); + EXPECT_EQ(GURL(kTestURL).GetOrigin(), blacklisted_credential.url); } TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Proxy) { @@ -428,7 +430,7 @@ TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Proxy) { EXPECT_TRUE(blacklisted_credential.blacklisted_by_user); EXPECT_EQ(PasswordForm::Scheme::kBasic, blacklisted_credential.scheme); EXPECT_EQ(kTestProxySignonRealm, blacklisted_credential.signon_realm); - EXPECT_EQ(GURL(kTestProxyOrigin), blacklisted_credential.origin); + EXPECT_EQ(GURL(kTestProxyOrigin), blacklisted_credential.url); } TEST(PasswordManagerUtil, ManualGenerationShouldNotReauthIfNotNeeded) { @@ -450,9 +452,13 @@ TEST(PasswordManagerUtil, ShouldShowAccountStorageOptIn) .WillByDefault(Return(true)); - EXPECT_CALL(mock_client, TriggerReauthForPrimaryAccount) + EXPECT_CALL( + mock_client, + TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint::kGeneratePasswordContextMenu, _)) .WillOnce( - [](base::OnceCallback<void( + [](signin_metrics::ReauthAccessPoint, + base::OnceCallback<void( password_manager::PasswordManagerClient::ReauthSucceeded)> callback) { std::move(callback).Run( @@ -470,9 +476,13 @@ TEST(PasswordManagerUtil, ShouldShowAccountStorageOptIn) .WillByDefault(Return(true)); - EXPECT_CALL(mock_client, TriggerReauthForPrimaryAccount) + EXPECT_CALL( + mock_client, + TriggerReauthForPrimaryAccount( + signin_metrics::ReauthAccessPoint::kGeneratePasswordContextMenu, _)) .WillOnce( - [](base::OnceCallback<void( + [](signin_metrics::ReauthAccessPoint, + base::OnceCallback<void( password_manager::PasswordManagerClient::ReauthSucceeded)> callback) { std::move(callback).Run( diff --git a/chromium/components/password_manager/core/browser/password_reuse_detection_manager.cc b/chromium/components/password_manager/core/browser/password_reuse_detection_manager.cc index 3a83ff44a80..bb4dcd18ae9 100644 --- a/chromium/components/password_manager/core/browser/password_reuse_detection_manager.cc +++ b/chromium/components/password_manager/core/browser/password_reuse_detection_manager.cc @@ -29,7 +29,7 @@ PasswordReuseDetectionManager::PasswordReuseDetectionManager( DCHECK(client_); } -PasswordReuseDetectionManager::~PasswordReuseDetectionManager() {} +PasswordReuseDetectionManager::~PasswordReuseDetectionManager() = default; void PasswordReuseDetectionManager::DidNavigateMainFrame( const GURL& main_frame_url) { diff --git a/chromium/components/password_manager/core/browser/password_reuse_detector_consumer.cc b/chromium/components/password_manager/core/browser/password_reuse_detector_consumer.cc index 9f64c86fdb1..8e1b392fc9b 100644 --- a/chromium/components/password_manager/core/browser/password_reuse_detector_consumer.cc +++ b/chromium/components/password_manager/core/browser/password_reuse_detector_consumer.cc @@ -6,8 +6,8 @@ namespace password_manager { -PasswordReuseDetectorConsumer::PasswordReuseDetectorConsumer() {} +PasswordReuseDetectorConsumer::PasswordReuseDetectorConsumer() = default; -PasswordReuseDetectorConsumer::~PasswordReuseDetectorConsumer() {} +PasswordReuseDetectorConsumer::~PasswordReuseDetectorConsumer() = default; } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_save_manager.h b/chromium/components/password_manager/core/browser/password_save_manager.h index a0c7b8a876b..d6d7ad5cf81 100644 --- a/chromium/components/password_manager/core/browser/password_save_manager.h +++ b/chromium/components/password_manager/core/browser/password_save_manager.h @@ -16,6 +16,10 @@ class GaiaIdHash; namespace password_manager { +namespace metrics_util { +enum class MoveToAccountStoreTrigger; +} + class PasswordManagerClient; class FormFetcher; class VotesUploader; @@ -81,7 +85,10 @@ class PasswordSaveManager { // Moves the pending credentials together with any other PSL matched ones from // the profile store to the account store. - virtual void MoveCredentialsToAccountStore() = 0; + // |trigger| represents the user action that triggered the flow and is used + // for recording metrics. + virtual void MoveCredentialsToAccountStore( + metrics_util::MoveToAccountStoreTrigger trigger) = 0; // Adds the |gaia_id_hash| to the |moving_blocked_for_list| of the // PasswordForm returned by GetPendingCredentials() and stores it in the diff --git a/chromium/components/password_manager/core/browser/password_save_manager_impl.cc b/chromium/components/password_manager/core/browser/password_save_manager_impl.cc index 86f8527ec66..962b3237177 100644 --- a/chromium/components/password_manager/core/browser/password_save_manager_impl.cc +++ b/chromium/components/password_manager/core/browser/password_save_manager_impl.cc @@ -171,7 +171,8 @@ void PasswordSaveManagerImpl::CreatePendingCredentials( submitted_form, generated_password, is_http_auth, is_credential_api_save, similar_saved_form); - SetVotesAndRecordMetricsForPendingCredentials(parsed_submitted_form); + if (votes_uploader_) + SetVotesAndRecordMetricsForPendingCredentials(parsed_submitted_form); } void PasswordSaveManagerImpl::SetVotesAndRecordMetricsForPendingCredentials( @@ -314,7 +315,8 @@ void PasswordSaveManagerImpl::PasswordNoLongerGenerated() { PasswordFormMetricsRecorder::GeneratedPasswordStatus::kPasswordDeleted); } -void PasswordSaveManagerImpl::MoveCredentialsToAccountStore() { +void PasswordSaveManagerImpl::MoveCredentialsToAccountStore( + metrics_util::MoveToAccountStoreTrigger) { // Moving credentials is only supported in MultiStorePasswordSaveManager. NOTREACHED(); } @@ -403,7 +405,7 @@ PasswordForm PasswordSaveManagerImpl::BuildPendingCredentials( password_manager_util::UpdateMetadataForUsage(&pending_credentials); // Update |pending_credentials| in order to be able correctly save it. - pending_credentials.origin = parsed_submitted_form.origin; + pending_credentials.url = parsed_submitted_form.url; pending_credentials.signon_realm = parsed_submitted_form.signon_realm; pending_credentials.action = parsed_submitted_form.action; break; diff --git a/chromium/components/password_manager/core/browser/password_save_manager_impl.h b/chromium/components/password_manager/core/browser/password_save_manager_impl.h index dc9018ad8b0..c5195f12663 100644 --- a/chromium/components/password_manager/core/browser/password_save_manager_impl.h +++ b/chromium/components/password_manager/core/browser/password_save_manager_impl.h @@ -33,6 +33,7 @@ class PasswordSaveManagerImpl : public PasswordSaveManager { const base::string16& GetGeneratedPassword() const override; FormSaver* GetFormSaver() const override; + // |metrics_recorder| and |votes_uploader| can both be nullptr. void Init(PasswordManagerClient* client, const FormFetcher* form_fetcher, scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder, @@ -71,7 +72,8 @@ class PasswordSaveManagerImpl : public PasswordSaveManager { // Signals that the user cancels password generation. void PasswordNoLongerGenerated() override; - void MoveCredentialsToAccountStore() override; + void MoveCredentialsToAccountStore( + metrics_util::MoveToAccountStoreTrigger) override; void BlockMovingToAccountStoreFor( const autofill::GaiaIdHash& gaia_id_hash) override; @@ -159,9 +161,10 @@ class PasswordSaveManagerImpl : public PasswordSaveManager { // Handles the user flows related to the generation. std::unique_ptr<PasswordGenerationManager> generation_manager_; - // Takes care of recording metrics and events for |*this|. + // Takes care of recording metrics and events for |*this|. Can be nullptr. scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder_; + // Can be nullptr. VotesUploader* votes_uploader_; }; diff --git a/chromium/components/password_manager/core/browser/password_save_manager_impl_unittest.cc b/chromium/components/password_manager/core/browser/password_save_manager_impl_unittest.cc index c8e29774877..45e4c5652dd 100644 --- a/chromium/components/password_manager/core/browser/password_save_manager_impl_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_save_manager_impl_unittest.cc @@ -58,7 +58,7 @@ MATCHER_P(FormHasUniqueKey, key, "") { void CheckPendingCredentials(const PasswordForm& expected, const PasswordForm& actual) { EXPECT_EQ(expected.signon_realm, actual.signon_realm); - EXPECT_EQ(expected.origin, actual.origin); + EXPECT_EQ(expected.url, actual.url); EXPECT_EQ(expected.action, actual.action); EXPECT_EQ(expected.username_value, actual.username_value); EXPECT_EQ(expected.password_value, actual.password_value); @@ -116,55 +116,54 @@ void CheckPasswordGenerationUKM(const ukm::TestAutoSetUkmRecorder& recorder, class MockFormSaver : public StubFormSaver { public: - MockFormSaver() = default; - - ~MockFormSaver() override = default; - // FormSaver: - MOCK_METHOD1(PermanentlyBlacklist, PasswordForm(PasswordStore::FormDigest)); - MOCK_METHOD1(Unblacklist, void(const PasswordStore::FormDigest&)); - MOCK_METHOD3(Save, - void(PasswordForm pending, - const std::vector<const PasswordForm*>& matches, - const base::string16& old_password)); - MOCK_METHOD3(Update, - void(PasswordForm pending, - const std::vector<const PasswordForm*>& matches, - const base::string16& old_password)); - MOCK_METHOD4(UpdateReplace, - void(PasswordForm pending, - const std::vector<const PasswordForm*>& matches, - const base::string16& old_password, - const PasswordForm& old_unique_key)); - MOCK_METHOD1(Remove, void(const PasswordForm&)); + MOCK_METHOD(PasswordForm, + PermanentlyBlacklist, + (PasswordStore::FormDigest), + (override)); + MOCK_METHOD(void, + Unblacklist, + (const PasswordStore::FormDigest&), + (override)); + MOCK_METHOD(void, + Save, + (PasswordForm pending, + const std::vector<const PasswordForm*>& matches, + const base::string16& old_password), + (override)); + MOCK_METHOD(void, + Update, + (PasswordForm pending, + const std::vector<const PasswordForm*>& matches, + const base::string16& old_password), + (override)); + MOCK_METHOD(void, + UpdateReplace, + (PasswordForm pending, + const std::vector<const PasswordForm*>& matches, + const base::string16& old_password, + const PasswordForm& old_unique_key), + (override)); + MOCK_METHOD(void, Remove, (const PasswordForm&), (override)); std::unique_ptr<FormSaver> Clone() override { return std::make_unique<MockFormSaver>(); } - - private: - DISALLOW_COPY_AND_ASSIGN(MockFormSaver); }; class MockPasswordManagerClient : public StubPasswordManagerClient { public: - MockPasswordManagerClient() = default; - ~MockPasswordManagerClient() override = default; - - MOCK_CONST_METHOD0(IsIncognito, bool()); - - MOCK_METHOD0(GetAutofillDownloadManager, - autofill::AutofillDownloadManager*()); - - MOCK_METHOD0(UpdateFormManagers, void()); - - MOCK_METHOD2(AutofillHttpAuth, - void(const PasswordForm&, const PasswordFormManagerForUI*)); - - MOCK_CONST_METHOD0(IsMainFrameSecure, bool()); - - private: - DISALLOW_COPY_AND_ASSIGN(MockPasswordManagerClient); + MOCK_METHOD(bool, IsIncognito, (), (const, override)); + MOCK_METHOD(autofill::AutofillDownloadManager*, + GetAutofillDownloadManager, + (), + (override)); + MOCK_METHOD(void, UpdateFormManagers, (), (override)); + MOCK_METHOD(void, + AutofillHttpAuth, + (const PasswordForm&, const PasswordFormManagerForUI*), + (override)); + MOCK_METHOD(bool, IsCommittedMainFrameSecure, (), (const, override)); }; class MockAutofillDownloadManager : public autofill::AutofillDownloadManager { @@ -172,19 +171,21 @@ class MockAutofillDownloadManager : public autofill::AutofillDownloadManager { MockAutofillDownloadManager() : AutofillDownloadManager(nullptr, &fake_observer) {} - MOCK_METHOD6(StartUploadRequest, - bool(const FormStructure&, - bool, - const autofill::ServerFieldTypeSet&, - const std::string&, - bool, - PrefService*)); + MOCK_METHOD(bool, + StartUploadRequest, + (const FormStructure&, + bool, + const autofill::ServerFieldTypeSet&, + const std::string&, + bool, + PrefService*), + (override)); private: class StubObserver : public AutofillDownloadManager::Observer { void OnLoadedServerPredictions( std::string response, - const std::vector<std::string>& form_signatures) override {} + const autofill::FormAndFieldSignatures& form_signatures) override {} }; StubObserver fake_observer; @@ -261,7 +262,7 @@ class PasswordSaveManagerImplTest : public testing::Test, submitted_form_.fields[kUsernameFieldIndex].value = ASCIIToUTF16("user1"); submitted_form_.fields[kPasswordFieldIndex].value = ASCIIToUTF16("secret1"); - saved_match_.origin = origin; + saved_match_.url = origin; saved_match_.action = action; saved_match_.signon_realm = "https://accounts.google.com/"; saved_match_.username_value = ASCIIToUTF16("test@gmail.com"); @@ -273,7 +274,7 @@ class PasswordSaveManagerImplTest : public testing::Test, saved_match_.in_store = PasswordForm::Store::kProfileStore; psl_saved_match_ = saved_match_; - psl_saved_match_.origin = psl_origin; + psl_saved_match_.url = psl_origin; psl_saved_match_.action = psl_action; psl_saved_match_.signon_realm = "https://myaccounts.google.com/"; psl_saved_match_.is_public_suffix_match = true; @@ -296,7 +297,8 @@ class PasswordSaveManagerImplTest : public testing::Test, fetcher_->Fetch(); metrics_recorder_ = base::MakeRefCounted<PasswordFormMetricsRecorder>( - client_.IsMainFrameSecure(), client_.GetUkmSourceId()); + client_.IsCommittedMainFrameSecure(), client_.GetUkmSourceId(), + /*pref_service=*/nullptr); auto mock_form_saver = std::make_unique<NiceMock<MockFormSaver>>(); mock_form_saver_ = mock_form_saver.get(); @@ -449,7 +451,7 @@ TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsAlreadySaved) { TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsPSLMatchSaved) { PasswordForm expected = saved_match_; - saved_match_.origin = GURL("https://m.accounts.google.com/auth"); + saved_match_.url = GURL("https://m.accounts.google.com/auth"); saved_match_.signon_realm = "https://m.accounts.google.com/"; saved_match_.is_public_suffix_match = true; @@ -599,7 +601,7 @@ TEST_P(PasswordSaveManagerImplTest, SaveNewCredentials) { password_save_manager_impl()->Save(observed_form_, Parse(submitted_form)); std::string expected_signon_realm = submitted_form.url.GetOrigin().spec(); - EXPECT_EQ(submitted_form.url, saved_form.origin); + EXPECT_EQ(submitted_form.url, saved_form.url); EXPECT_EQ(expected_signon_realm, saved_form.signon_realm); EXPECT_EQ(new_username, saved_form.username_value); EXPECT_EQ(new_password, saved_form.password_value); @@ -649,7 +651,7 @@ TEST_P(PasswordSaveManagerImplTest, SavePSLToAlreadySaved) { password_save_manager_impl()->Save(observed_form_, Parse(submitted_form)); - EXPECT_EQ(submitted_form.url, saved_form.origin); + EXPECT_EQ(submitted_form.url, saved_form.url); EXPECT_EQ(GetSignonRealm(submitted_form.url), saved_form.signon_realm); EXPECT_EQ(psl_saved_match_.username_value, saved_form.username_value); EXPECT_EQ(psl_saved_match_.password_value, saved_form.password_value); diff --git a/chromium/components/password_manager/core/browser/password_store.cc b/chromium/components/password_manager/core/browser/password_store.cc index 0c6c3a8f3dd..93d9670c59d 100644 --- a/chromium/components/password_manager/core/browser/password_store.cc +++ b/chromium/components/password_manager/core/browser/password_store.cc @@ -14,6 +14,7 @@ #include "base/debug/dump_without_crashing.h" #include "base/location.h" #include "base/macros.h" +#include "base/memory/ptr_util.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/stl_util.h" @@ -52,6 +53,8 @@ namespace password_manager { namespace { +const base::TimeDelta kSyncTaskTimeout = base::TimeDelta::FromSeconds(30); + // Utility function to simplify removing logins prior a given |cutoff| data. // Runs |callback| with the result. // @@ -90,7 +93,7 @@ PasswordStore::CheckReuseRequest::CheckReuseRequest( TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("passwords", "CheckReuseRequest", this); } -PasswordStore::CheckReuseRequest::~CheckReuseRequest() {} +PasswordStore::CheckReuseRequest::~CheckReuseRequest() = default; void PasswordStore::CheckReuseRequest::OnReuseFound( size_t password_length, @@ -108,18 +111,16 @@ void PasswordStore::CheckReuseRequest::OnReuseFound( 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) {} + const GURL& new_url) + : scheme(new_scheme), signon_realm(new_signon_realm), url(new_url) {} PasswordStore::FormDigest::FormDigest(const PasswordForm& form) - : scheme(form.scheme), - signon_realm(form.signon_realm), - origin(form.origin) {} + : scheme(form.scheme), signon_realm(form.signon_realm), url(form.url) {} PasswordStore::FormDigest::FormDigest(const autofill::FormData& form) : scheme(PasswordForm::Scheme::kHtml), signon_realm(form.url.GetOrigin().spec()), - origin(form.url) {} + url(form.url) {} PasswordStore::FormDigest::FormDigest(const FormDigest& other) = default; @@ -133,13 +134,11 @@ PasswordStore::FormDigest& PasswordStore::FormDigest::operator=( bool PasswordStore::FormDigest::operator==(const FormDigest& other) const { return scheme == other.scheme && signon_realm == other.signon_realm && - origin == other.origin; + url == other.url; } PasswordStore::PasswordStore() - : observers_(new base::ObserverListThreadSafe<Observer>()), - shutdown_called_(false), - init_status_(InitStatus::kUnknown) {} + : observers_(new base::ObserverListThreadSafe<Observer>()) {} bool PasswordStore::Init(PrefService* prefs, base::RepeatingClosure sync_enabled_or_disabled_cb) { @@ -196,11 +195,13 @@ void PasswordStore::RemoveLoginsByURLAndTime( const base::RepeatingCallback<bool(const GURL&)>& url_filter, base::Time delete_begin, base::Time delete_end, - base::OnceClosure completion) { + base::OnceClosure completion, + base::OnceCallback<void(bool)> sync_completion) { DCHECK(main_task_runner_->RunsTasksInCurrentSequence()); ScheduleTask(base::BindOnce(&PasswordStore::RemoveLoginsByURLAndTimeInternal, this, url_filter, delete_begin, delete_end, - std::move(completion))); + std::move(completion), + std::move(sync_completion))); } void PasswordStore::RemoveLoginsCreatedBetween(base::Time delete_begin, @@ -667,7 +668,7 @@ PasswordStore::CreateBackgroundTaskRunner() const { bool PasswordStore::InitOnBackgroundSequence() { DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); - sync_bridge_.reset(new PasswordSyncBridge( + sync_bridge_ = base::WrapUnique(new PasswordSyncBridge( std::make_unique<syncer::ClientTagBasedModelTypeProcessor>( syncer::PASSWORDS, base::DoNothing()), /*password_store_sync=*/this, sync_enabled_or_disabled_cb_)); @@ -677,8 +678,8 @@ bool PasswordStore::InitOnBackgroundSequence() { base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, - base::BindOnce(&PasswordStoreConsumer::OnGetPasswordStoreResults, - reuse_detector_->GetWeakPtr(), + base::BindOnce(&PasswordStoreConsumer::OnGetPasswordStoreResultsFrom, + reuse_detector_->GetWeakPtr(), base::WrapRefCounted(this), GetAutofillableLoginsImpl())); #endif return true; @@ -746,6 +747,25 @@ void PasswordStore::NotifyLoginsChanged( } } +void PasswordStore::NotifyDeletionsHaveSynced(bool success) { + DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); + // Either all deletions have been committed to the Sync server, or Sync is + // telling us that it won't commit them (because Sync was turned off + // permanently). In either case, run the corresponding callbacks now (on the + // main task runner). + DCHECK(!success || !GetMetadataStore()->HasUnsyncedDeletions()); + if (!deletions_have_synced_callbacks_.empty()) { + base::UmaHistogramBoolean( + "PasswordManager.PasswordStoreDeletionsHaveSynced", success); + } + for (auto& callback : deletions_have_synced_callbacks_) { + main_task_runner_->PostTask(FROM_HERE, + base::BindOnce(std::move(callback), success)); + } + deletions_have_synced_timeout_.Cancel(); + deletions_have_synced_callbacks_.clear(); +} + void PasswordStore::InvokeAndNotifyAboutCompromisedPasswordsChange( base::OnceCallback<bool()> callback) { DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); @@ -858,8 +878,8 @@ void PasswordStore::PostLoginsTaskAndReplyToConsumerWithResult( LoginsTask task) { consumer->cancelable_task_tracker()->PostTaskAndReplyWithResult( background_task_runner_.get(), FROM_HERE, std::move(task), - base::BindOnce(&PasswordStoreConsumer::OnGetPasswordStoreResults, - consumer->GetWeakPtr())); + base::BindOnce(&PasswordStoreConsumer::OnGetPasswordStoreResultsFrom, + consumer->GetWeakPtr(), base::WrapRefCounted(this))); } void PasswordStore::PostLoginsTaskAndReplyToConsumerWithProcessedResult( @@ -869,8 +889,8 @@ void PasswordStore::PostLoginsTaskAndReplyToConsumerWithProcessedResult( LoginsResultProcessor processor) { auto call_consumer = base::BindOnce( CloseTraceAndCallBack, trace_name, consumer, - base::BindOnce(&PasswordStoreConsumer::OnGetPasswordStoreResults, - consumer->GetWeakPtr())); + base::BindOnce(&PasswordStoreConsumer::OnGetPasswordStoreResultsFrom, + consumer->GetWeakPtr(), base::WrapRefCounted(this))); consumer->cancelable_task_tracker()->PostTaskAndReplyWithResult( background_task_runner_.get(), FROM_HERE, std::move(task), base::BindOnce(std::move(processor), std::move(call_consumer))); @@ -955,7 +975,8 @@ void PasswordStore::RemoveLoginsByURLAndTimeInternal( const base::RepeatingCallback<bool(const GURL&)>& url_filter, base::Time delete_begin, base::Time delete_end, - base::OnceClosure completion) { + base::OnceClosure completion, + base::OnceCallback<void(bool)> sync_completion) { DCHECK(background_task_runner_->RunsTasksInCurrentSequence()); TRACE_EVENT0("passwords", "PasswordStore::RemoveLoginsByURLAndTimeInternal"); BeginTransaction(); @@ -967,8 +988,23 @@ void PasswordStore::RemoveLoginsByURLAndTimeInternal( // sync codebase needs to update metadata atomically together with the login // data. CommitTransaction(); + if (completion) main_task_runner_->PostTask(FROM_HERE, std::move(completion)); + + if (sync_completion) { + deletions_have_synced_callbacks_.push_back(std::move(sync_completion)); + // Start a timeout for sync, or restart it if it was already running. + deletions_have_synced_timeout_.Reset(base::BindRepeating( + &PasswordStore::NotifyDeletionsHaveSynced, this, /*success=*/false)); + background_task_runner_->PostDelayedTask( + FROM_HERE, deletions_have_synced_timeout_.callback(), kSyncTaskTimeout); + + // Do an immediate check for the case where there are already no unsynced + // deletions. + if (!GetMetadataStore()->HasUnsyncedDeletions()) + NotifyDeletionsHaveSynced(/*success=*/true); + } } void PasswordStore::RemoveLoginsCreatedBetweenInternal( @@ -1315,7 +1351,7 @@ std::ostream& operator<<(std::ostream& os, const PasswordStore::FormDigest& digest) { return os << "FormDigest(scheme: " << digest.scheme << ", signon_realm: " << digest.signon_realm - << ", origin: " << digest.origin << ")"; + << ", url: " << digest.url << ")"; } } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/password_store.h b/chromium/components/password_manager/core/browser/password_store.h index 34a0d57af92..44a20a0d47c 100644 --- a/chromium/components/password_manager/core/browser/password_store.h +++ b/chromium/components/password_manager/core/browser/password_store.h @@ -13,6 +13,7 @@ #include "base/bind_helpers.h" #include "base/callback.h" #include "base/callback_list.h" +#include "base/cancelable_callback.h" #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/scoped_refptr.h" @@ -120,7 +121,7 @@ class PasswordStore : protected PasswordStoreSync, struct FormDigest { FormDigest(autofill::PasswordForm::Scheme scheme, const std::string& signon_realm, - const GURL& origin); + const GURL& url); explicit FormDigest(const autofill::PasswordForm& form); explicit FormDigest(const autofill::FormData& form); FormDigest(const FormDigest& other); @@ -131,7 +132,7 @@ class PasswordStore : protected PasswordStoreSync, autofill::PasswordForm::Scheme scheme; std::string signon_realm; - GURL origin; + GURL url; }; PasswordStore(); @@ -159,7 +160,7 @@ class PasswordStore : protected PasswordStoreSync, virtual void AddLogin(const autofill::PasswordForm& form); // Updates the matching PasswordForm in the secure password store (async). - // If any of the primary key fields (signon_realm, origin, username_element, + // If any of the primary key fields (signon_realm, url, username_element, // username_value, password_element) are updated, then the second version of // the method must be used that takes |old_primary_key|, i.e., the old values // for the primary key fields (the rest of the fields are ignored). @@ -172,15 +173,20 @@ class PasswordStore : protected PasswordStoreSync, virtual void RemoveLogin(const autofill::PasswordForm& form); // Remove all logins whose origins match the given filter and that were - // created - // in the given date range. |completion| will be posted to the - // |main_task_runner_| after deletions have been completed and notification - // have been sent out. + // created in the given date range. |completion| will be posted to the + // |main_task_runner_| after deletions have been completed and notifications + // have been sent out. |sync_completion| will be posted to + // |main_task_runner_| once the deletions have also been propagated to the + // server (or, in rare cases, if the user permanently disables Sync or + // deletions haven't been propagated after 30 seconds). This is + // only relevant for Sync users and for account store users - for other users, + // |sync_completion| will be run immediately after |completion|. void RemoveLoginsByURLAndTime( const base::RepeatingCallback<bool(const GURL&)>& url_filter, base::Time delete_begin, base::Time delete_end, - base::OnceClosure completion); + base::OnceClosure completion, + base::OnceCallback<void(bool)> sync_completion = base::NullCallback()); // Removes all logins created in the given date range. If |completion| is not // null, it will be posted to the |main_task_runner_| after deletions have @@ -252,6 +258,7 @@ class PasswordStore : protected PasswordStoreSync, // Adds or replaces the statistics for the domain |stats.origin_domain|. void AddSiteStats(const InteractionsStats& stats); + // TODO(crbug/1081389): replace GURL with Origin. // Removes the statistics for |origin_domain|. void RemoveSiteStats(const GURL& origin_domain); @@ -572,6 +579,8 @@ class PasswordStore : protected PasswordStoreSync, // been changed. void NotifyLoginsChanged(const PasswordStoreChangeList& changes) override; + void NotifyDeletionsHaveSynced(bool success) override; + void NotifyUnsyncedCredentialsWillBeDeleted( const std::vector<autofill::PasswordForm>& unsynced_credentials) override; @@ -699,7 +708,8 @@ class PasswordStore : protected PasswordStoreSync, const base::RepeatingCallback<bool(const GURL&)>& url_filter, base::Time delete_begin, base::Time delete_end, - base::OnceClosure completion); + base::OnceClosure completion, + base::OnceCallback<void(bool)> sync_completion); void RemoveLoginsCreatedBetweenInternal(base::Time delete_begin, base::Time delete_end, base::OnceClosure completion); @@ -786,7 +796,7 @@ class PasswordStore : protected PasswordStoreSync, const std::vector<std::string>& additional_android_realms); // Retrieves the currently stored form, if any, with the same primary key as - // |form|, that is, with the same signon_realm, origin, username_element, + // |form|, that is, with the same signon_realm, url, username_element, // username_value and password_element attributes. To be called on the // background sequence. std::unique_ptr<autofill::PasswordForm> GetLoginImpl( @@ -859,9 +869,16 @@ class PasswordStore : protected PasswordStoreSync, std::unique_ptr<UnsyncedCredentialsDeletionNotifier> deletion_notifier_; - bool shutdown_called_; + // A list of callbacks that should be run once all pending deletions have been + // sent to the Sync server. Note that the vector itself lives on the + // background thread, but the callbacks must be run on the main thread! + std::vector<base::OnceCallback<void(bool)>> deletions_have_synced_callbacks_; + // Timeout closure that runs if sync takes too long to propagate deletions. + base::CancelableClosure deletions_have_synced_timeout_; + + bool shutdown_called_ = false; - InitStatus init_status_; + InitStatus init_status_ = InitStatus::kUnknown; DISALLOW_COPY_AND_ASSIGN(PasswordStore); }; diff --git a/chromium/components/password_manager/core/browser/password_store_change.h b/chromium/components/password_manager/core/browser/password_store_change.h index d65a55e136f..c24eb873792 100644 --- a/chromium/components/password_manager/core/browser/password_store_change.h +++ b/chromium/components/password_manager/core/browser/password_store_change.h @@ -47,7 +47,7 @@ class PasswordStoreChange { bool operator==(const PasswordStoreChange& other) const { return type() == other.type() && form().signon_realm == other.form().signon_realm && - form().origin == other.form().origin && + form().url == other.form().url && form().action == other.form().action && form().submit_element == other.form().submit_element && form().username_element == other.form().username_element && diff --git a/chromium/components/password_manager/core/browser/password_store_consumer.cc b/chromium/components/password_manager/core/browser/password_store_consumer.cc index e4e4d4b047a..e40cb07e15a 100644 --- a/chromium/components/password_manager/core/browser/password_store_consumer.cc +++ b/chromium/components/password_manager/core/browser/password_store_consumer.cc @@ -4,7 +4,9 @@ #include "components/password_manager/core/browser/password_store_consumer.h" +#include "components/autofill/core/common/password_form.h" #include "components/password_manager/core/browser/field_info_table.h" +#include "components/password_manager/core/browser/password_store.h" #include "components/password_manager/core/browser/statistics_table.h" namespace password_manager { @@ -13,6 +15,12 @@ PasswordStoreConsumer::PasswordStoreConsumer() = default; PasswordStoreConsumer::~PasswordStoreConsumer() = default; +void PasswordStoreConsumer::OnGetPasswordStoreResultsFrom( + scoped_refptr<PasswordStore> store, + std::vector<std::unique_ptr<autofill::PasswordForm>> results) { + OnGetPasswordStoreResults(std::move(results)); +} + void PasswordStoreConsumer::OnGetSiteStatistics( std::vector<InteractionsStats> stats) {} diff --git a/chromium/components/password_manager/core/browser/password_store_consumer.h b/chromium/components/password_manager/core/browser/password_store_consumer.h index 5e8e25df508..75c4c8d47f3 100644 --- a/chromium/components/password_manager/core/browser/password_store_consumer.h +++ b/chromium/components/password_manager/core/browser/password_store_consumer.h @@ -8,6 +8,8 @@ #include <memory> #include <vector> +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" #include "base/task/cancelable_task_tracker.h" namespace autofill { @@ -18,6 +20,7 @@ namespace password_manager { struct FieldInfo; struct InteractionsStats; +class PasswordStore; // Reads from the PasswordStore are done asynchronously on a separate // thread. PasswordStoreConsumer provides the virtual callback method, which is @@ -33,6 +36,15 @@ class PasswordStoreConsumer { virtual void OnGetPasswordStoreResults( std::vector<std::unique_ptr<autofill::PasswordForm>> results) = 0; + // Like OnGetPasswordStoreResults(), but also receives the originating + // PasswordStore as a parameter. This is useful for consumers that query both + // the profile-scoped and the account-scoped store. + // The default implementation simply calls OnGetPasswordStoreResults(), so + // consumers that don't care about the store can just ignore this. + virtual void OnGetPasswordStoreResultsFrom( + scoped_refptr<PasswordStore> store, + std::vector<std::unique_ptr<autofill::PasswordForm>> results); + // Called when the GetSiteStats() request is finished, with the associated // site statistics. virtual void OnGetSiteStatistics(std::vector<InteractionsStats> stats); diff --git a/chromium/components/password_manager/core/browser/password_store_default.cc b/chromium/components/password_manager/core/browser/password_store_default.cc index dc17fe82aef..bd797187541 100644 --- a/chromium/components/password_manager/core/browser/password_store_default.cc +++ b/chromium/components/password_manager/core/browser/password_store_default.cc @@ -22,8 +22,7 @@ PasswordStoreDefault::PasswordStoreDefault( std::unique_ptr<LoginDatabase> login_db) : login_db_(std::move(login_db)) {} -PasswordStoreDefault::~PasswordStoreDefault() { -} +PasswordStoreDefault::~PasswordStoreDefault() = default; void PasswordStoreDefault::ShutdownOnUIThread() { PasswordStore::ShutdownOnUIThread(); @@ -41,6 +40,11 @@ bool PasswordStoreDefault::InitOnBackgroundSequence() { success = false; LOG(ERROR) << "Could not create/open login database."; } + if (success) { + login_db_->SetDeletionsHaveSyncedCallback( + base::BindRepeating(&PasswordStoreDefault::NotifyDeletionsHaveSynced, + base::Unretained(this))); + } return PasswordStore::InitOnBackgroundSequence() && success; } @@ -102,7 +106,7 @@ PasswordStoreChangeList PasswordStoreDefault::RemoveLoginsByURLAndTimeImpl( for (const auto& pair : key_to_form_map) { PasswordForm* form = pair.second.get(); PasswordStoreChangeList remove_changes; - if (url_filter.Run(form->origin) && + if (url_filter.Run(form->url) && login_db_->RemoveLogin(*form, &remove_changes)) { std::move(remove_changes.begin(), remove_changes.end(), std::back_inserter(changes)); @@ -132,8 +136,8 @@ PasswordStoreChangeList PasswordStoreDefault::DisableAutoSignInForOriginsImpl( std::set<GURL> origins_to_update; for (const auto& pair : key_to_form_map) { - if (origin_filter.Run(pair.second->origin)) - origins_to_update.insert(pair.second->origin); + if (origin_filter.Run(pair.second->url)) + origins_to_update.insert(pair.second->url); } std::set<GURL> origins_updated; @@ -143,7 +147,7 @@ PasswordStoreChangeList PasswordStoreDefault::DisableAutoSignInForOriginsImpl( } for (const auto& pair : key_to_form_map) { - if (origins_updated.count(pair.second->origin)) { + if (origins_updated.count(pair.second->url)) { changes.emplace_back(PasswordStoreChange::UPDATE, *pair.second, /*primary_key=*/pair.first); } diff --git a/chromium/components/password_manager/core/browser/password_store_default_unittest.cc b/chromium/components/password_manager/core/browser/password_store_default_unittest.cc index 0be0cec5822..21d86f86a06 100644 --- a/chromium/components/password_manager/core/browser/password_store_default_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_store_default_unittest.cc @@ -53,7 +53,6 @@ class MockPasswordStoreConsumer : public PasswordStoreConsumer { class BadLoginDatabase : public LoginDatabase { public: BadLoginDatabase() : LoginDatabase(base::FilePath(), IsAccountStore(false)) {} - ~BadLoginDatabase() override {} // LoginDatabase: bool Init() override { return false; } @@ -173,8 +172,8 @@ TEST(PasswordStoreDefaultTest, NonASCIIData) { // Build the expected forms vector and add the forms to the store. std::vector<std::unique_ptr<PasswordForm>> expected_forms; - for (unsigned int i = 0; i < base::size(form_data); ++i) { - expected_forms.push_back(FillPasswordFormWithData(form_data[i])); + for (const auto& data : form_data) { + expected_forms.push_back(FillPasswordFormWithData(data)); store->AddLogin(*expected_forms.back()); } @@ -256,7 +255,7 @@ TEST(PasswordStoreDefaultTest, OperationsOnABadDatabaseSilentlyFail) { FillPasswordFormWithData(CreateTestPasswordFormData()); std::unique_ptr<PasswordForm> blacklisted_form(new PasswordForm(*form)); blacklisted_form->signon_realm = "http://foo.example.com"; - blacklisted_form->origin = GURL("http://foo.example.com/origin"); + blacklisted_form->url = GURL("http://foo.example.com/origin"); blacklisted_form->action = GURL("http://foo.example.com/action"); blacklisted_form->blacklisted_by_user = true; bad_store->AddLogin(*form); diff --git a/chromium/components/password_manager/core/browser/password_store_signin_notifier.cc b/chromium/components/password_manager/core/browser/password_store_signin_notifier.cc index bebd9ee425a..80300be5a34 100644 --- a/chromium/components/password_manager/core/browser/password_store_signin_notifier.cc +++ b/chromium/components/password_manager/core/browser/password_store_signin_notifier.cc @@ -9,9 +9,9 @@ namespace password_manager { -PasswordStoreSigninNotifier::PasswordStoreSigninNotifier() {} +PasswordStoreSigninNotifier::PasswordStoreSigninNotifier() = default; -PasswordStoreSigninNotifier::~PasswordStoreSigninNotifier() {} +PasswordStoreSigninNotifier::~PasswordStoreSigninNotifier() = default; void PasswordStoreSigninNotifier::NotifySignedOut(const std::string& username, bool primary_account) { diff --git a/chromium/components/password_manager/core/browser/password_store_sync.h b/chromium/components/password_manager/core/browser/password_store_sync.h index ac9361a7bd9..277bfa59908 100644 --- a/chromium/components/password_manager/core/browser/password_store_sync.h +++ b/chromium/components/password_manager/core/browser/password_store_sync.h @@ -102,6 +102,20 @@ class PasswordStoreSync { // Deletes all the stored sync metadata for passwords. virtual void DeleteAllSyncMetadata() = 0; + + // Registers a callback that will be invoked whenever all pending (unsynced) + // deletions are gone. If they were committed to the server (or, rarely, the + // entity was undeleted), the |callback| will be run with "true". If the + // deletions are gone because Sync was permanently turned off, it'll be run + // with "false" instead. + // Note that there can be only one such callback; if one was already + // registered, it'll be overridden by the new |callback|. + virtual void SetDeletionsHaveSyncedCallback( + base::RepeatingCallback<void(bool)> callback) = 0; + + // Returns whether there are any pending deletions that have not been sent + // to the Sync server yet. + virtual bool HasUnsyncedDeletions() = 0; }; PasswordStoreSync(); @@ -149,6 +163,11 @@ class PasswordStoreSync { // Notifies observers that password store data may have been changed. virtual void NotifyLoginsChanged(const PasswordStoreChangeList& changes) = 0; + // Notifies any waiting callback that all pending deletions have been + // committed to the Sync server now, or that Sync definitely won't commit + // them (because Sync was turned off permanently). + virtual void NotifyDeletionsHaveSynced(bool success) = 0; + // Notifies the UI that some unsynced credentials will be deleted on sign-out // in order to offer the user the option of saving them in the profile store. // Should only be called for the account store. diff --git a/chromium/components/password_manager/core/browser/password_store_unittest.cc b/chromium/components/password_manager/core/browser/password_store_unittest.cc index 35bdcee8365..ea1d567caee 100644 --- a/chromium/components/password_manager/core/browser/password_store_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_store_unittest.cc @@ -209,8 +209,8 @@ TEST_F(PasswordStoreTest, IgnoreOldWwwGoogleLogins) { // Build the forms vector and add the forms to the store. std::vector<std::unique_ptr<PasswordForm>> all_forms; - for (size_t i = 0; i < base::size(form_data); ++i) { - all_forms.push_back(FillPasswordFormWithData(form_data[i])); + for (const auto& data : form_data) { + all_forms.push_back(FillPasswordFormWithData(data)); store->AddLogin(*all_forms.back()); } @@ -296,7 +296,7 @@ TEST_F(PasswordStoreTest, UpdateLoginPrimaryKeyFields) { EXPECT_CALL(mock_observer, OnLoginsChanged(testing::SizeIs(2u))); PasswordForm old_primary_key; old_primary_key.signon_realm = old_form->signon_realm; - old_primary_key.origin = old_form->origin; + old_primary_key.url = old_form->url; old_primary_key.username_element = old_form->username_element; old_primary_key.username_value = old_form->username_value; old_primary_key.password_element = old_form->password_element; @@ -601,8 +601,8 @@ TEST_F(PasswordStoreTest, GetLoginsWithoutAffiliations) { store->Init(nullptr); std::vector<std::unique_ptr<PasswordForm>> all_credentials; - for (size_t i = 0; i < base::size(kTestCredentials); ++i) { - all_credentials.push_back(FillPasswordFormWithData(kTestCredentials[i])); + for (const auto& credential : kTestCredentials) { + all_credentials.push_back(FillPasswordFormWithData(credential)); store->AddLogin(*all_credentials.back()); } @@ -869,8 +869,8 @@ TEST_F(PasswordStoreTest, UpdatePasswordsStoredForAffiliatedWebsites) { // Set up the initial test data set. std::vector<std::unique_ptr<PasswordForm>> all_credentials; - for (size_t i = 0; i < base::size(kTestCredentials); ++i) { - all_credentials.push_back(FillPasswordFormWithData(kTestCredentials[i])); + for (const auto& credential : kTestCredentials) { + all_credentials.push_back(FillPasswordFormWithData(credential)); all_credentials.back()->date_synced = all_credentials.back()->date_created; store->AddLogin(*all_credentials.back()); diff --git a/chromium/components/password_manager/core/browser/password_sync_util.cc b/chromium/components/password_manager/core/browser/password_sync_util.cc index 3b9a791794d..6d903aeec68 100644 --- a/chromium/components/password_manager/core/browser/password_sync_util.cc +++ b/chromium/components/password_manager/core/browser/password_sync_util.cc @@ -89,8 +89,8 @@ bool IsGaiaCredentialPage(const std::string& signon_realm) { bool ShouldSaveEnterprisePasswordHash(const autofill::PasswordForm& form, const PrefService& prefs) { #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) - return safe_browsing::MatchesPasswordProtectionLoginURL(form.origin, prefs) || - safe_browsing::MatchesPasswordProtectionChangePasswordURL(form.origin, + return safe_browsing::MatchesPasswordProtectionLoginURL(form.url, prefs) || + safe_browsing::MatchesPasswordProtectionChangePasswordURL(form.url, prefs); #else return false; diff --git a/chromium/components/password_manager/core/browser/password_ui_utils.cc b/chromium/components/password_manager/core/browser/password_ui_utils.cc index 63d03583112..bda49377d3e 100644 --- a/chromium/components/password_manager/core/browser/password_ui_utils.cc +++ b/chromium/components/password_manager/core/browser/password_ui_utils.cc @@ -50,16 +50,16 @@ std::pair<std::string, GURL> GetShownOriginAndLinkUrl( : password_form.app_display_name; link_url = GURL(kPlayStoreAppPrefix + facet_uri.android_package_name()); } else { - shown_origin = GetShownOrigin(password_form.origin); - link_url = password_form.origin; + shown_origin = GetShownOrigin(url::Origin::Create(password_form.url)); + link_url = password_form.url; } return {std::move(shown_origin), std::move(link_url)}; } -std::string GetShownOrigin(const GURL& origin) { +std::string GetShownOrigin(const url::Origin& origin) { std::string original = - base::UTF16ToUTF8(url_formatter::FormatUrlForSecurityDisplay( + base::UTF16ToUTF8(url_formatter::FormatOriginForSecurityDisplay( origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)); base::StringPiece result = original; for (base::StringPiece prefix : kRemovedPrefixes) { diff --git a/chromium/components/password_manager/core/browser/password_ui_utils.h b/chromium/components/password_manager/core/browser/password_ui_utils.h index 18a4fd7467a..9dd2808b379 100644 --- a/chromium/components/password_manager/core/browser/password_ui_utils.h +++ b/chromium/components/password_manager/core/browser/password_ui_utils.h @@ -12,7 +12,7 @@ #include "base/strings/string_piece.h" -#include "url/gurl.h" +#include "url/origin.h" namespace autofill { struct PasswordForm; @@ -41,7 +41,7 @@ std::pair<std::string, GURL> GetShownOriginAndLinkUrl( // Returns a string suitable for security display to the user (just like // |FormatUrlForSecurityDisplay| with OMIT_HTTP_AND_HTTPS) based on origin of // |password_form|) and without prefixes "m.", "mobile." or "www.". -std::string GetShownOrigin(const GURL& origin); +std::string GetShownOrigin(const url::Origin& origin); // Updates the |form_manager| pending credentials with |username| and // |password|. diff --git a/chromium/components/password_manager/core/browser/password_ui_utils_unittest.cc b/chromium/components/password_manager/core/browser/password_ui_utils_unittest.cc index 4ba9a9eb588..8dc03e1c128 100644 --- a/chromium/components/password_manager/core/browser/password_ui_utils_unittest.cc +++ b/chromium/components/password_manager/core/browser/password_ui_utils_unittest.cc @@ -33,9 +33,8 @@ TEST(GetShownOriginTest, RemovePrefixes) { {"https://WWW.Example.DE", "example.de"}}; for (const auto& test_case : kTestCases) { - autofill::PasswordForm password_form; - password_form.origin = GURL(test_case.input); - EXPECT_EQ(test_case.output, GetShownOrigin(password_form.origin)) + EXPECT_EQ(test_case.output, + GetShownOrigin(url::Origin::Create(GURL(test_case.input)))) << "for input " << test_case.input; } } @@ -71,7 +70,7 @@ TEST(GetShownOriginAndLinkUrlTest, OriginFromAndroidForm_WithAppDisplayName) { TEST(GetShownOriginAndLinkUrlTest, OriginFromNonAndroidForm) { autofill::PasswordForm form; form.signon_realm = "https://example.com/"; - form.origin = GURL("https://example.com/login?ref=1"); + form.url = GURL("https://example.com/login?ref=1"); std::string shown_origin; GURL link_url; diff --git a/chromium/components/password_manager/core/browser/psl_matching_helper.cc b/chromium/components/password_manager/core/browser/psl_matching_helper.cc index b7dca4c71f2..b945e2b3309 100644 --- a/chromium/components/password_manager/core/browser/psl_matching_helper.cc +++ b/chromium/components/password_manager/core/browser/psl_matching_helper.cc @@ -43,22 +43,21 @@ std::ostream& operator<<(std::ostream& out, MatchResult result) { return out; } -bool IsFederatedRealm(const std::string& form_signon_realm, - const GURL& origin) { +bool IsFederatedRealm(const std::string& form_signon_realm, const GURL& url) { // The format should be "federation://origin.host/federation.host; - std::string federated_realm = "federation://" + origin.host() + "/"; + std::string federated_realm = "federation://" + url.host() + "/"; return form_signon_realm.size() > federated_realm.size() && base::StartsWith(form_signon_realm, federated_realm, base::CompareCase::INSENSITIVE_ASCII); } bool IsFederatedPSLMatch(const std::string& form_signon_realm, - const GURL& form_origin, - const GURL& origin) { - if (!IsPublicSuffixDomainMatch(form_origin.spec(), origin.spec())) + const GURL& form_url, + const GURL& url) { + if (!IsPublicSuffixDomainMatch(form_url.spec(), url.spec())) return false; - return IsFederatedRealm(form_signon_realm, form_origin); + return IsFederatedRealm(form_signon_realm, form_url); } MatchResult GetMatchResult(const PasswordForm& form, @@ -77,13 +76,13 @@ MatchResult GetMatchResult(const PasswordForm& form, const bool allow_federated_match = !form.federation_origin.opaque(); if (allow_federated_match && - IsFederatedRealm(form.signon_realm, form_digest.origin) && - form.origin.GetOrigin() == form_digest.origin.GetOrigin()) { + IsFederatedRealm(form.signon_realm, form_digest.url) && + form.url.GetOrigin() == form_digest.url.GetOrigin()) { return MatchResult::FEDERATED_MATCH; } if (allow_federated_match && - IsFederatedPSLMatch(form.signon_realm, form.origin, form_digest.origin)) { + IsFederatedPSLMatch(form.signon_realm, form.url, form_digest.url)) { return MatchResult::FEDERATED_PSL_MATCH; } diff --git a/chromium/components/password_manager/core/browser/psl_matching_helper_unittest.cc b/chromium/components/password_manager/core/browser/psl_matching_helper_unittest.cc index 84a703f88c4..2a436193006 100644 --- a/chromium/components/password_manager/core/browser/psl_matching_helper_unittest.cc +++ b/chromium/components/password_manager/core/browser/psl_matching_helper_unittest.cc @@ -65,8 +65,8 @@ TEST(PSLMatchingUtilsTest, GetMatchResultNormalCredentials) { for (const TestData& data : cases) { autofill::PasswordForm form; - form.origin = GURL(data.form_origin); - form.signon_realm = form.origin.GetOrigin().spec(); + form.url = GURL(data.form_origin); + form.signon_realm = form.url.GetOrigin().spec(); PasswordStore::FormDigest digest( autofill::PasswordForm::Scheme::kHtml, GURL(data.digest_origin).GetOrigin().spec(), GURL(data.digest_origin)); @@ -122,8 +122,8 @@ TEST(PSLMatchingUtilsTest, GetMatchResultPSL) { for (const TestData& data : cases) { autofill::PasswordForm form; - form.origin = GURL(data.form_origin); - form.signon_realm = form.origin.GetOrigin().spec(); + form.url = GURL(data.form_origin); + form.signon_realm = form.url.GetOrigin().spec(); PasswordStore::FormDigest digest( autofill::PasswordForm::Scheme::kHtml, GURL(data.digest_origin).GetOrigin().spec(), GURL(data.digest_origin)); @@ -182,11 +182,11 @@ TEST(PSLMatchingUtilsTest, GetMatchResultFederated) { for (const TestData& data : cases) { autofill::PasswordForm form; - form.origin = GURL(data.form_origin); + form.url = GURL(data.form_origin); form.federation_origin = url::Origin::Create(GURL(data.form_federation_origin)); - form.signon_realm = "federation://" + form.origin.host() + "/" + - form.federation_origin.host(); + form.signon_realm = + "federation://" + form.url.host() + "/" + form.federation_origin.host(); PasswordStore::FormDigest digest( autofill::PasswordForm::Scheme::kHtml, @@ -253,11 +253,11 @@ TEST(PSLMatchingUtilsTest, GetMatchResultFederatedPSL) { for (const TestData& data : cases) { autofill::PasswordForm form; - form.origin = GURL(data.form_origin); + form.url = GURL(data.form_origin); form.federation_origin = url::Origin::Create(GURL(data.form_federation_origin)); - form.signon_realm = "federation://" + form.origin.host() + "/" + - form.federation_origin.host(); + form.signon_realm = + "federation://" + form.url.host() + "/" + form.federation_origin.host(); PasswordStore::FormDigest digest( autofill::PasswordForm::Scheme::kHtml, diff --git a/chromium/components/password_manager/core/browser/sql_table_builder_unittest.cc b/chromium/components/password_manager/core/browser/sql_table_builder_unittest.cc index d35786dad4b..f29bd197e63 100644 --- a/chromium/components/password_manager/core/browser/sql_table_builder_unittest.cc +++ b/chromium/components/password_manager/core/browser/sql_table_builder_unittest.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/logging.h" #include "base/macros.h" #include "base/test/mock_callback.h" #include "sql/database.h" diff --git a/chromium/components/password_manager/core/browser/statistics_table.cc b/chromium/components/password_manager/core/browser/statistics_table.cc index 4975fcc199d..82439375f32 100644 --- a/chromium/components/password_manager/core/browser/statistics_table.cc +++ b/chromium/components/password_manager/core/browser/statistics_table.cc @@ -28,7 +28,7 @@ enum LoginTableColumns { std::vector<InteractionsStats> StatementToInteractionsStats(sql::Statement* s) { std::vector<InteractionsStats> results; while (s->Step()) { - results.push_back(InteractionsStats()); + results.emplace_back(); results.back().origin_domain = GURL(s->ColumnString(COLUMN_ORIGIN_DOMAIN)); results.back().username_value = s->ColumnString16(COLUMN_USERNAME); results.back().dismissal_count = s->ColumnInt(COLUMN_DISMISSALS); diff --git a/chromium/components/password_manager/core/browser/statistics_table_unittest.cc b/chromium/components/password_manager/core/browser/statistics_table_unittest.cc index 8fb520a0e9b..198332aba9d 100644 --- a/chromium/components/password_manager/core/browser/statistics_table_unittest.cc +++ b/chromium/components/password_manager/core/browser/statistics_table_unittest.cc @@ -4,6 +4,7 @@ #include "components/password_manager/core/browser/statistics_table.h" #include <functional> +#include <memory> #include "base/bind.h" #include "base/bind_helpers.h" @@ -45,8 +46,8 @@ class StatisticsTableTest : public testing::Test { void ReloadDatabase() { base::FilePath file = temp_dir_.GetPath().AppendASCII("TestDatabase"); - db_.reset(new StatisticsTable); - connection_.reset(new sql::Database); + db_ = std::make_unique<StatisticsTable>(); + connection_ = std::make_unique<sql::Database>(); connection_->set_exclusive_locking(); ASSERT_TRUE(connection_->Open(file)); db_->Init(connection_.get()); diff --git a/chromium/components/password_manager/core/browser/store_metrics_reporter.cc b/chromium/components/password_manager/core/browser/store_metrics_reporter.cc index f38f31953f4..b8fe07c928a 100644 --- a/chromium/components/password_manager/core/browser/store_metrics_reporter.cc +++ b/chromium/components/password_manager/core/browser/store_metrics_reporter.cc @@ -5,6 +5,8 @@ #include "components/password_manager/core/browser/store_metrics_reporter.h" #include "base/metrics/histogram_functions.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "components/password_manager/core/browser/password_manager_client.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/password_store.h" @@ -13,19 +15,169 @@ namespace password_manager { +namespace { + +class Consumer : public PasswordStoreConsumer { + public: + explicit Consumer(PasswordStore* store, + base::OnceCallback<void( + std::vector<std::unique_ptr<autofill::PasswordForm>>)> + results_callback) + : results_callback_(std::move(results_callback)) { + store->GetAutofillableLogins(this); + } + ~Consumer() override = default; + + void OnGetPasswordStoreResults( + std::vector<std::unique_ptr<autofill::PasswordForm>> results) override { + std::move(results_callback_).Run(std::move(results)); + // Note: |this| might be destroyed now. + } + + private: + base::OnceCallback<void(std::vector<std::unique_ptr<autofill::PasswordForm>>)> + results_callback_; +}; + +} // namespace + +class StoreMetricsReporter::MultiStoreMetricsReporter { + public: + MultiStoreMetricsReporter(PasswordStore* profile_store, + PasswordStore* account_store, + base::OnceClosure done_callback) + : done_callback_(std::move(done_callback)) { + DCHECK(profile_store); + DCHECK(account_store); + profile_store_consumer_ = std::make_unique<Consumer>( + profile_store, + base::BindOnce(&MultiStoreMetricsReporter::ReceiveResults, + base::Unretained(this), /*is_account_store=*/false)); + account_store_consumer_ = std::make_unique<Consumer>( + account_store, + base::BindOnce(&MultiStoreMetricsReporter::ReceiveResults, + base::Unretained(this), /*is_account_store=*/true)); + } + + private: + void ReceiveResults( + bool is_account_store, + std::vector<std::unique_ptr<autofill::PasswordForm>> results) { + if (is_account_store) { + for (const std::unique_ptr<autofill::PasswordForm>& form : results) { + account_store_results_.insert(std::make_pair( + std::make_pair(form->signon_realm, form->username_value), + form->password_value)); + } + account_store_consumer_.reset(); + } else { + for (const std::unique_ptr<autofill::PasswordForm>& form : results) { + profile_store_results_.insert(std::make_pair( + std::make_pair(form->signon_realm, form->username_value), + form->password_value)); + } + profile_store_consumer_.reset(); + } + + if (!profile_store_consumer_ && !account_store_consumer_) + ReportMetrics(); + } + + void ReportMetrics() { + // Count the contents of the account store as compared to the profile store: + // - Additional: Credentials that are in the account store, but not in the + // profile store. + // - Missing: Credentials that are in the profile store, but not in the + // account store. + // - Identical: Credentials that are in both stores. + // - Conflicting: Credentials with the same signon realm and username, but + // different passwords in the two stores. + int additional = 0; + int missing = 0; + int identical = 0; + int conflicting = 0; + + // Go over the data from both stores in parallel, always advancing in the + // one that is "behind". The entries are sorted by signon_realm and + // username (the exact ordering doesn't matter, just that it's consistent). + auto profile_it = profile_store_results_.begin(); + auto account_it = account_store_results_.begin(); + while (account_it != account_store_results_.end()) { + // First, go over any entries in the profile store that don't exist in the + // account store. + while (profile_it != profile_store_results_.end() && + profile_it->first < account_it->first) { + ++missing; + ++profile_it; + } + // Now profile_it->first is >= account_it->first. + // Check if they match. + if (profile_it != profile_store_results_.end() && + account_it->first == profile_it->first) { + // The signon_realm and username match, check the password value. + if (account_it->second == profile_it->second) + ++identical; + else + ++conflicting; + + ++profile_it; + } else { + // The signon_realm and username don't match, so this is an account + // store entry that doesn't exist in the profile store. + ++additional; + } + + ++account_it; + } + // We're done with the account store. Go over any remaining profile store + // entries. + while (profile_it != profile_store_results_.end()) { + ++missing; + ++profile_it; + } + + base::UmaHistogramCounts100( + "PasswordManager.AccountStoreVsProfileStore.Additional", additional); + base::UmaHistogramCounts100( + "PasswordManager.AccountStoreVsProfileStore.Missing", missing); + base::UmaHistogramCounts100( + "PasswordManager.AccountStoreVsProfileStore.Identical", identical); + base::UmaHistogramCounts100( + "PasswordManager.AccountStoreVsProfileStore.Conflicting", conflicting); + + std::move(done_callback_).Run(); + // Note: |this| might be destroyed now. + } + + base::OnceClosure done_callback_; + + std::unique_ptr<Consumer> profile_store_consumer_; + std::unique_ptr<Consumer> account_store_consumer_; + + // Maps from (signon_realm, username) to password. + std::map<std::pair<std::string, base::string16>, base::string16> + profile_store_results_; + std::map<std::pair<std::string, base::string16>, base::string16> + account_store_results_; +}; + StoreMetricsReporter::StoreMetricsReporter( PasswordManagerClient* client, const syncer::SyncService* sync_service, const signin::IdentityManager* identity_manager, PrefService* prefs) { - // May be null in tests. - if (PasswordStore* store = client->GetProfilePasswordStore()) { - store->ReportMetrics( - password_manager::sync_util::GetSyncUsernameIfSyncingPasswords( - sync_service, identity_manager), - client->GetPasswordSyncState() == - password_manager::SYNCING_WITH_CUSTOM_PASSPHRASE, - client->IsUnderAdvancedProtection()); + for (PasswordStore* store : + {client->GetProfilePasswordStore(), client->GetAccountPasswordStore()}) { + // May be null in tests. The account store is also null if the + // kEnablePasswordsAccountStorage feature is disabled. + if (store) { + store->ReportMetrics( + password_manager::sync_util::GetSyncUsernameIfSyncingPasswords( + sync_service, identity_manager), + client->GetPasswordSyncState() == + password_manager::SYNCING_WITH_CUSTOM_PASSPHRASE, + client->IsUnderAdvancedProtection()); + } } base::UmaHistogramBoolean( "PasswordManager.Enabled", @@ -34,6 +186,38 @@ StoreMetricsReporter::StoreMetricsReporter( "PasswordManager.LeakDetection.Enabled", prefs->GetBoolean( password_manager::prefs::kPasswordLeakDetectionEnabled)); + + // If both stores exist, kick off the MultiStoreMetricsReporter. + PasswordStore* profile_store = client->GetProfilePasswordStore(); + PasswordStore* account_store = client->GetAccountPasswordStore(); + if (profile_store && account_store) { + // Delay the actual reporting by 30 seconds, to ensure it doesn't happen + // during the "hot phase" of Chrome startup. (This is what + // PasswordStore::ReportMetrics also does.) + // Grab refptrs to the stores, to ensure they're still alive when the + // delayed task runs. + scoped_refptr<PasswordStore> retained_profile_store = profile_store; + scoped_refptr<PasswordStore> retained_account_store = account_store; + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&StoreMetricsReporter::ReportMultiStoreMetrics, + weak_ptr_factory_.GetWeakPtr(), retained_profile_store, + retained_account_store), + base::TimeDelta::FromSeconds(30)); + } +} + +void StoreMetricsReporter::ReportMultiStoreMetrics( + scoped_refptr<PasswordStore> profile_store, + scoped_refptr<PasswordStore> account_store) { + multi_store_reporter_ = std::make_unique<MultiStoreMetricsReporter>( + profile_store.get(), account_store.get(), + base::BindOnce(&StoreMetricsReporter::MultiStoreMetricsDone, + weak_ptr_factory_.GetWeakPtr())); +} + +void StoreMetricsReporter::MultiStoreMetricsDone() { + multi_store_reporter_.reset(); } StoreMetricsReporter::~StoreMetricsReporter() = default; diff --git a/chromium/components/password_manager/core/browser/store_metrics_reporter.h b/chromium/components/password_manager/core/browser/store_metrics_reporter.h index 21d0ed2831b..5c017fbcf15 100644 --- a/chromium/components/password_manager/core/browser/store_metrics_reporter.h +++ b/chromium/components/password_manager/core/browser/store_metrics_reporter.h @@ -5,7 +5,11 @@ #ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_STORE_METRICS_REPORTER_H_ #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_STORE_METRICS_REPORTER_H_ +#include <memory> + #include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "components/password_manager/core/browser/password_store.h" class PrefService; @@ -34,7 +38,7 @@ class StoreMetricsReporter { // |client| to obtain the password store and password syncing state. Uses // |sync_service| and |identity_manager| to obtain the sync username to report // about its presence among saved credentials. Uses the |prefs| to obtain - // information wither the password manager and the leak detection feature is + // information whether the password manager and the leak detection feature is // enabled. StoreMetricsReporter(PasswordManagerClient* client, const syncer::SyncService* sync_service, @@ -44,6 +48,16 @@ class StoreMetricsReporter { ~StoreMetricsReporter(); private: + class MultiStoreMetricsReporter; + + void ReportMultiStoreMetrics(scoped_refptr<PasswordStore> profile_store, + scoped_refptr<PasswordStore> account_store); + void MultiStoreMetricsDone(); + + std::unique_ptr<MultiStoreMetricsReporter> multi_store_reporter_; + + base::WeakPtrFactory<StoreMetricsReporter> weak_ptr_factory_{this}; + DISALLOW_COPY_AND_ASSIGN(StoreMetricsReporter); }; diff --git a/chromium/components/password_manager/core/browser/store_metrics_reporter_unittest.cc b/chromium/components/password_manager/core/browser/store_metrics_reporter_unittest.cc index 3ad7ab30ed7..969961a0a04 100644 --- a/chromium/components/password_manager/core/browser/store_metrics_reporter_unittest.cc +++ b/chromium/components/password_manager/core/browser/store_metrics_reporter_unittest.cc @@ -6,11 +6,13 @@ #include "base/macros.h" #include "base/memory/scoped_refptr.h" +#include "base/strings/utf_string_conversions.h" #include "base/test/metrics/histogram_tester.h" #include "components/password_manager/core/browser/mock_password_store.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" #include "components/password_manager/core/browser/stub_password_manager_client.h" #include "components/password_manager/core/browser/sync_username_test_base.h" +#include "components/password_manager/core/browser/test_password_store.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" @@ -24,20 +26,25 @@ using ::testing::Return; namespace password_manager { namespace { +autofill::PasswordForm CreateForm(const std::string& signon_realm, + const std::string& username, + const std::string& password) { + autofill::PasswordForm form; + form.signon_realm = signon_realm; + form.username_value = base::ASCIIToUTF16(username); + form.password_value = base::ASCIIToUTF16(password); + return form; +} + class MockPasswordManagerClient : public StubPasswordManagerClient { public: MOCK_CONST_METHOD0(GetProfilePasswordStore, PasswordStore*()); + MOCK_CONST_METHOD0(GetAccountPasswordStore, PasswordStore*()); MOCK_CONST_METHOD0(GetPasswordSyncState, SyncState()); MOCK_CONST_METHOD0(IsUnderAdvancedProtection, bool()); }; -// The test fixture defines two tests, one that doesn't require a password store -// and one that does. Each of these tests depend on two boolean parameters, -// which are declared here. Each test then assigns the desired semantics to -// them. -class StoreMetricsReporterTest - : public SyncUsernameTestBase, - public ::testing::WithParamInterface<std::tuple<bool, bool>> { +class StoreMetricsReporterTest : public SyncUsernameTestBase { public: StoreMetricsReporterTest() { prefs_.registry()->RegisterBooleanPref(prefs::kCredentialsEnableService, @@ -56,8 +63,16 @@ class StoreMetricsReporterTest DISALLOW_COPY_AND_ASSIGN(StoreMetricsReporterTest); }; +// The test fixture defines two tests, one that doesn't require a password store +// and one that does. Each of these tests depend on two boolean parameters, +// which are declared here. Each test then assigns the desired semantics to +// them. +class StoreMetricsReporterTestWithParams + : public StoreMetricsReporterTest, + public ::testing::WithParamInterface<std::tuple<bool, bool>> {}; + // Test that store-independent metrics are reported correctly. -TEST_P(StoreMetricsReporterTest, StoreIndependentMetrics) { +TEST_P(StoreMetricsReporterTestWithParams, StoreIndependentMetrics) { const bool password_manager_enabled = std::get<0>(GetParam()); const bool leak_detection_enabled = std::get<1>(GetParam()); @@ -66,7 +81,8 @@ TEST_P(StoreMetricsReporterTest, StoreIndependentMetrics) { prefs_.SetBoolean(password_manager::prefs::kPasswordLeakDetectionEnabled, leak_detection_enabled); base::HistogramTester histogram_tester; - EXPECT_CALL(client_, GetProfilePasswordStore()).WillOnce(Return(nullptr)); + EXPECT_CALL(client_, GetProfilePasswordStore()) + .WillRepeatedly(Return(nullptr)); StoreMetricsReporter reporter(&client_, sync_service(), identity_manager(), &prefs_); @@ -78,7 +94,7 @@ TEST_P(StoreMetricsReporterTest, StoreIndependentMetrics) { // Test that sync username and syncing state are passed correctly to the // PasswordStore. -TEST_P(StoreMetricsReporterTest, StoreDependentMetrics) { +TEST_P(StoreMetricsReporterTestWithParams, StoreDependentMetrics) { const bool syncing_with_passphrase = std::get<0>(GetParam()); const bool is_under_advanced_protection = std::get<1>(GetParam()); @@ -86,10 +102,12 @@ TEST_P(StoreMetricsReporterTest, StoreDependentMetrics) { const auto sync_state = syncing_with_passphrase ? password_manager::SYNCING_WITH_CUSTOM_PASSPHRASE : password_manager::SYNCING_NORMAL_ENCRYPTION; - EXPECT_CALL(client_, GetPasswordSyncState()).WillOnce(Return(sync_state)); - EXPECT_CALL(client_, GetProfilePasswordStore()).WillOnce(Return(store.get())); + EXPECT_CALL(client_, GetPasswordSyncState()) + .WillRepeatedly(Return(sync_state)); + EXPECT_CALL(client_, GetProfilePasswordStore()) + .WillRepeatedly(Return(store.get())); EXPECT_CALL(client_, IsUnderAdvancedProtection()) - .WillOnce(Return(is_under_advanced_protection)); + .WillRepeatedly(Return(is_under_advanced_protection)); EXPECT_CALL(*store, ReportMetrics("some.user@gmail.com", syncing_with_passphrase, is_under_advanced_protection)); @@ -100,8 +118,98 @@ TEST_P(StoreMetricsReporterTest, StoreDependentMetrics) { store->ShutdownOnUIThread(); } +// A test that covers multi-store metrics, which are recorded by the +// StoreMetricsReporter directly. +TEST_F(StoreMetricsReporterTest, MultiStoreMetrics) { + auto profile_store = + base::MakeRefCounted<TestPasswordStore>(/*is_account_store=*/false); + auto account_store = + base::MakeRefCounted<TestPasswordStore>(/*is_account_store=*/true); + profile_store->Init(&prefs_); + account_store->Init(&prefs_); + + EXPECT_CALL(client_, GetPasswordSyncState()) + .WillRepeatedly( + Return(password_manager::ACCOUNT_PASSWORDS_ACTIVE_NORMAL_ENCRYPTION)); + EXPECT_CALL(client_, IsUnderAdvancedProtection()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(client_, GetProfilePasswordStore()) + .WillRepeatedly(Return(profile_store.get())); + EXPECT_CALL(client_, GetAccountPasswordStore()) + .WillRepeatedly(Return(account_store.get())); + + const std::string kRealm1 = "https://example.com"; + const std::string kRealm2 = "https://example2.com"; + + // Add test data to the profile store: + // - 3 credentials that don't exist in the account store + // - 1 credential that conflicts with the account store (exists there with the + // same username but different password) + // - 2 credentials with identical copies in the account store + // Note: In the implementation, the credentials are processed in alphabetical + // order of usernames. Choose usernames here so that some profile-store-only + // credentials end up at both the start and the end of the list, to make sure + // these cases are handled correctly. + profile_store->AddLogin( + CreateForm(kRealm1, "aprofileuser1", "aprofilepass1")); + profile_store->AddLogin( + CreateForm(kRealm1, "aprofileuser2", "aprofilepass2")); + profile_store->AddLogin( + CreateForm(kRealm1, "zprofileuser3", "zprofilepass3")); + profile_store->AddLogin(CreateForm(kRealm1, "conflictinguser", "localpass")); + profile_store->AddLogin( + CreateForm(kRealm1, "identicaluser1", "identicalpass1")); + profile_store->AddLogin( + CreateForm(kRealm1, "identicaluser2", "identicalpass2")); + + // Add test data to the account store: + // - 2 credentials that don't exist in the account store + // - 1 credential that conflicts with the profile store (exists there with the + // same username but different password) + // - 2 credentials with identical copies in the profile store + account_store->AddLogin(CreateForm(kRealm1, "accountuser1", "accountpass1")); + account_store->AddLogin( + CreateForm(kRealm1, "zaccountuser2", "zaccountpass2")); + account_store->AddLogin( + CreateForm(kRealm1, "conflictinguser", "accountpass")); + account_store->AddLogin( + CreateForm(kRealm1, "identicaluser1", "identicalpass1")); + account_store->AddLogin( + CreateForm(kRealm1, "identicaluser2", "identicalpass2")); + + // Finally, add one more identical credential to the profile store. However + // this one is on a different signon realm, so should be counted as just + // another (4th) credential that's missing in the account store. + profile_store->AddLogin( + CreateForm(kRealm2, "identicaluser1", "identicalpass1")); + + base::HistogramTester histogram_tester; + + StoreMetricsReporter reporter(&client_, sync_service(), identity_manager(), + &prefs_); + // Wait for the metrics to get reported. This is delayed by 30 seconds, and + // then involves queries to the stores, i.e. to background task runners. + FastForwardBy(base::TimeDelta::FromSeconds(30)); + RunUntilIdle(); + + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStoreVsProfileStore.Additional", 2, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStoreVsProfileStore.Missing", 4, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStoreVsProfileStore.Identical", 2, 1); + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStoreVsProfileStore.Conflicting", 1, 1); + + account_store->ShutdownOnUIThread(); + profile_store->ShutdownOnUIThread(); + // Make sure the PasswordStore destruction parts on the background sequence + // finish, otherwise we get memory leak reports. + RunUntilIdle(); +} + INSTANTIATE_TEST_SUITE_P(All, - StoreMetricsReporterTest, + StoreMetricsReporterTestWithParams, testing::Combine(Bool(), Bool())); } // namespace } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/stub_password_manager_client.cc b/chromium/components/password_manager/core/browser/stub_password_manager_client.cc index c6c3db72252..9faf43bf97e 100644 --- a/chromium/components/password_manager/core/browser/stub_password_manager_client.cc +++ b/chromium/components/password_manager/core/browser/stub_password_manager_client.cc @@ -15,7 +15,7 @@ namespace password_manager { StubPasswordManagerClient::StubPasswordManagerClient() : ukm_source_id_(ukm::UkmRecorder::GetNewSourceID()) {} -StubPasswordManagerClient::~StubPasswordManagerClient() {} +StubPasswordManagerClient::~StubPasswordManagerClient() = default; bool StubPasswordManagerClient::PromptUserToSaveOrUpdatePassword( std::unique_ptr<PasswordFormManagerForUI> form_to_save, @@ -44,14 +44,14 @@ void StubPasswordManagerClient::FocusedInputChanged( bool StubPasswordManagerClient::PromptUserToChooseCredentials( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin, + const url::Origin& origin, const CredentialsCallback& callback) { return false; } void StubPasswordManagerClient::NotifyUserAutoSignin( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin) {} + const url::Origin& origin) {} void StubPasswordManagerClient::NotifyUserCouldBeAutoSignedIn( std::unique_ptr<autofill::PasswordForm> form) {} @@ -76,10 +76,14 @@ PasswordStore* StubPasswordManagerClient::GetAccountPasswordStore() const { return nullptr; } -const GURL& StubPasswordManagerClient::GetLastCommittedEntryURL() const { +const GURL& StubPasswordManagerClient::GetLastCommittedURL() const { return GURL::EmptyGURL(); } +url::Origin StubPasswordManagerClient::GetLastCommittedOrigin() const { + return url::Origin(); +} + const CredentialsFilter* StubPasswordManagerClient::GetStoreResultFilter() const { return &credentials_filter_; @@ -132,7 +136,7 @@ ukm::SourceId StubPasswordManagerClient::GetUkmSourceId() { PasswordManagerMetricsRecorder* StubPasswordManagerClient::GetMetricsRecorder() { if (!metrics_recorder_) { - metrics_recorder_.emplace(GetUkmSourceId(), GetMainFrameURL(), nullptr); + metrics_recorder_.emplace(GetUkmSourceId(), nullptr); } return base::OptionalOrNullptr(metrics_recorder_); } diff --git a/chromium/components/password_manager/core/browser/stub_password_manager_client.h b/chromium/components/password_manager/core/browser/stub_password_manager_client.h index 69cc390c1e2..d6175706b5c 100644 --- a/chromium/components/password_manager/core/browser/stub_password_manager_client.h +++ b/chromium/components/password_manager/core/browser/stub_password_manager_client.h @@ -44,11 +44,11 @@ class StubPasswordManagerClient : public PasswordManagerClient { autofill::mojom::FocusedFieldType focused_field_type) override; bool PromptUserToChooseCredentials( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin, + const url::Origin& origin, const CredentialsCallback& callback) override; void NotifyUserAutoSignin( std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms, - const GURL& origin) override; + const url::Origin& origin) override; void NotifyUserCouldBeAutoSignedIn( std::unique_ptr<autofill::PasswordForm>) override; void NotifySuccessfulLoginWithExistingPassword( @@ -59,7 +59,8 @@ class StubPasswordManagerClient : public PasswordManagerClient { PrefService* GetPrefs() const override; PasswordStore* GetProfilePasswordStore() const override; PasswordStore* GetAccountPasswordStore() const override; - const GURL& GetLastCommittedEntryURL() const override; + const GURL& GetLastCommittedURL() const override; + url::Origin GetLastCommittedOrigin() const override; const CredentialsFilter* GetStoreResultFilter() const override; const autofill::LogManager* GetLogManager() const override; const MockPasswordFeatureManager* GetPasswordFeatureManager() const override; diff --git a/chromium/components/password_manager/core/browser/sync/password_model_type_controller.cc b/chromium/components/password_manager/core/browser/sync/password_model_type_controller.cc index feca14dc11a..6e1fd12ca49 100644 --- a/chromium/components/password_manager/core/browser/sync/password_model_type_controller.cc +++ b/chromium/components/password_manager/core/browser/sync/password_model_type_controller.cc @@ -8,6 +8,7 @@ #include "components/password_manager/core/browser/password_manager_features_util.h" #include "components/prefs/pref_service.h" +#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" #include "components/sync/base/model_type.h" #include "components/sync/driver/sync_client.h" #include "components/sync/driver/sync_service.h" @@ -81,8 +82,10 @@ syncer::DataTypeController::PreconditionState PasswordModelTypeController::GetPreconditionState() const { // 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()) + 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_) @@ -96,10 +99,44 @@ void PasswordModelTypeController::OnStateChanged(syncer::SyncService* sync) { state_changed_callback_.Run(); } +void PasswordModelTypeController::OnAccountsInCookieUpdated( + const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, + const GoogleServiceAuthError& error) { + // 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); +} + void PasswordModelTypeController::OnAccountsCookieDeletedByUserAction() { features_util::ClearAccountStorageSettingsForAllUsers(pref_service_); } +void PasswordModelTypeController::OnPrimaryAccountCleared( + const CoreAccountInfo& previous_primary_account_info) { + // Note: OnPrimaryAccountCleared() 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_, previous_primary_account_info.gaia); +} + 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 diff --git a/chromium/components/password_manager/core/browser/sync/password_model_type_controller.h b/chromium/components/password_manager/core/browser/sync/password_model_type_controller.h index a25be9fb73f..e3bfb46e6a5 100644 --- a/chromium/components/password_manager/core/browser/sync/password_model_type_controller.h +++ b/chromium/components/password_manager/core/browser/sync/password_model_type_controller.h @@ -50,7 +50,12 @@ class PasswordModelTypeController : public syncer::ModelTypeController, void OnStateChanged(syncer::SyncService* sync) override; // IdentityManager::Observer overrides. + void OnAccountsInCookieUpdated( + const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, + const GoogleServiceAuthError& error) override; void OnAccountsCookieDeletedByUserAction() override; + void OnPrimaryAccountCleared( + const CoreAccountInfo& previous_primary_account_info) override; private: void OnOptInStateMaybeChanged(); diff --git a/chromium/components/password_manager/core/browser/sync/password_sync_bridge.cc b/chromium/components/password_manager/core/browser/sync/password_sync_bridge.cc index 289defba7bc..ba059757c15 100644 --- a/chromium/components/password_manager/core/browser/sync/password_sync_bridge.cc +++ b/chromium/components/password_manager/core/browser/sync/password_sync_bridge.cc @@ -67,7 +67,7 @@ sync_pb::PasswordSpecifics SpecificsFromPassword( specifics.mutable_client_only_encrypted_data(); password_data->set_scheme(static_cast<int>(password_form.scheme)); password_data->set_signon_realm(password_form.signon_realm); - password_data->set_origin(password_form.origin.spec()); + password_data->set_origin(password_form.url.spec()); password_data->set_action(password_form.action.spec()); password_data->set_username_element( base::UTF16ToUTF8(password_form.username_element)); @@ -105,7 +105,7 @@ autofill::PasswordForm PasswordFromEntityChange( password.scheme = static_cast<autofill::PasswordForm::Scheme>(password_data.scheme()); password.signon_realm = password_data.signon_realm(); - password.origin = GURL(password_data.origin()); + password.url = GURL(password_data.origin()); password.action = GURL(password_data.action()); password.username_element = base::UTF8ToUTF16(password_data.username_element()); @@ -163,7 +163,7 @@ bool AreLocalAndRemotePasswordsEqual( return ( static_cast<int>(password_form.scheme) == password_specifics.scheme() && password_form.signon_realm == password_specifics.signon_realm() && - password_form.origin.spec() == password_specifics.origin() && + password_form.url.spec() == password_specifics.origin() && password_form.action.spec() == password_specifics.action() && base::UTF16ToUTF8(password_form.username_element) == password_specifics.username_element() && @@ -817,7 +817,8 @@ void PasswordSyncBridge::ApplyStopSyncChanges( const autofill::PasswordForm& form = *primary_key_and_form.second; password_store_changes.emplace_back(PasswordStoreChange::REMOVE, form, primary_key); - if (unsynced_passwords_storage_keys.count(primary_key) != 0) { + if (unsynced_passwords_storage_keys.count(primary_key) != 0 && + !form.blacklisted_by_user) { unsynced_logins_being_deleted.push_back(form); } } @@ -826,6 +827,10 @@ void PasswordSyncBridge::ApplyStopSyncChanges( password_store_sync_->DeleteAndRecreateDatabaseFile(); password_store_sync_->NotifyLoginsChanged(password_store_changes); + base::UmaHistogramCounts100( + "PasswordManager.AccountStorage.UnsyncedPasswordsFoundDuringSignOut", + unsynced_logins_being_deleted.size()); + if (!unsynced_logins_being_deleted.empty()) { password_store_sync_->NotifyUnsyncedCredentialsWillBeDeleted( unsynced_logins_being_deleted); diff --git a/chromium/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc b/chromium/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc index 7e5d29e68e2..ef3b6222a11 100644 --- a/chromium/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc +++ b/chromium/components/password_manager/core/browser/sync/password_sync_bridge_unittest.cc @@ -12,6 +12,7 @@ #include "base/bind_helpers.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind_test_util.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/mock_callback.h" #include "build/build_config.h" #include "components/password_manager/core/browser/password_store_sync.h" @@ -89,7 +90,7 @@ sync_pb::PasswordSpecifics CreateSpecificsWithSignonRealm( autofill::PasswordForm MakePasswordForm(const std::string& signon_realm) { autofill::PasswordForm form; - form.origin = GURL("http://www.origin.com"); + form.url = GURL("http://www.origin.com"); form.username_element = base::UTF8ToUTF16("username_element"); form.username_value = base::UTF8ToUTF16("username_value"); form.password_element = base::UTF8ToUTF16("password_element"); @@ -97,6 +98,14 @@ autofill::PasswordForm MakePasswordForm(const std::string& signon_realm) { return form; } +autofill::PasswordForm MakeBlacklistedForm(const std::string& signon_realm) { + autofill::PasswordForm form; + form.url = GURL("http://www.origin.com"); + form.signon_realm = signon_realm; + form.blacklisted_by_user = true; + return form; +} + // A mini database class the supports Add/Update/Remove functionality. It also // supports an auto increment primary key that starts from 1. It will be used to // empower the MockPasswordStoreSync be forwarding all database calls to an @@ -175,9 +184,6 @@ class FakeDatabase { class MockSyncMetadataStore : public PasswordStoreSync::MetadataStore { public: - MockSyncMetadataStore() = default; - ~MockSyncMetadataStore() = default; - MOCK_METHOD0(GetAllSyncMetadata, std::unique_ptr<syncer::MetadataBatch>()); MOCK_METHOD0(DeleteAllSyncMetadata, void()); MOCK_METHOD3(UpdateSyncMetadata, @@ -188,13 +194,13 @@ class MockSyncMetadataStore : public PasswordStoreSync::MetadataStore { MOCK_METHOD2(UpdateModelTypeState, bool(syncer::ModelType, const sync_pb::ModelTypeState&)); MOCK_METHOD1(ClearModelTypeState, bool(syncer::ModelType)); + MOCK_METHOD1(SetDeletionsHaveSyncedCallback, + void(base::RepeatingCallback<void(bool)>)); + MOCK_METHOD0(HasUnsyncedDeletions, bool()); }; class MockPasswordStoreSync : public PasswordStoreSync { public: - MockPasswordStoreSync() = default; - ~MockPasswordStoreSync() = default; - MOCK_METHOD1(FillAutofillableLogins, bool(std::vector<std::unique_ptr<autofill::PasswordForm>>*)); MOCK_METHOD1(FillBlacklistLogins, @@ -211,6 +217,8 @@ class MockPasswordStoreSync : public PasswordStoreSync { MOCK_METHOD1(RemoveLoginSync, PasswordStoreChangeList(const autofill::PasswordForm&)); MOCK_METHOD1(NotifyLoginsChanged, void(const PasswordStoreChangeList&)); + MOCK_METHOD1(NotifyDeletionsHaveSynced, void(bool)); + MOCK_METHOD1( NotifyUnsyncedCredentialsWillBeDeleted, void(const std::vector<autofill::PasswordForm>& unsynced_credentials)); @@ -273,8 +281,6 @@ class PasswordSyncBridgeTest : public testing::Test { return data; } - ~PasswordSyncBridgeTest() override {} - base::Optional<sync_pb::PasswordSpecifics> GetDataFromBridge( const std::string& storage_key) { std::unique_ptr<syncer::DataBatch> batch; @@ -899,18 +905,22 @@ TEST_F(PasswordSyncBridgeTest, ShouldNotNotifyOnSyncDisableIfProfileStore) { } TEST_F(PasswordSyncBridgeTest, ShouldNotifyUnsyncedCredentialsIfAccountStore) { + base::HistogramTester histogram_tester; ON_CALL(*mock_password_store_sync(), IsAccountStore()) .WillByDefault(Return(true)); const std::string kPrimaryKeyUnsyncedCredentialStr = "1000"; const std::string kPrimaryKeySyncedCredentialStr = "1001"; const std::string kPrimaryKeyUnsyncedDeletionStr = "1002"; + const std::string kPrimaryKeyUnsyncedBlacklistStr = "1003"; ON_CALL(mock_processor(), IsEntityUnsynced(kPrimaryKeyUnsyncedCredentialStr)) .WillByDefault(Return(true)); ON_CALL(mock_processor(), IsEntityUnsynced(kPrimaryKeySyncedCredentialStr)) .WillByDefault(Return(false)); ON_CALL(mock_processor(), IsEntityUnsynced(kPrimaryKeyUnsyncedDeletionStr)) .WillByDefault(Return(true)); + ON_CALL(mock_processor(), IsEntityUnsynced(kPrimaryKeyUnsyncedBlacklistStr)) + .WillByDefault(Return(true)); sync_pb::EntityMetadata is_deletion_metadata; is_deletion_metadata.set_is_deleted(true); @@ -928,6 +938,9 @@ TEST_F(PasswordSyncBridgeTest, ShouldNotifyUnsyncedCredentialsIfAccountStore) { batch->AddMetadata( kPrimaryKeyUnsyncedDeletionStr, std::make_unique<sync_pb::EntityMetadata>(is_deletion_metadata)); + batch->AddMetadata(kPrimaryKeyUnsyncedBlacklistStr, + std::make_unique<sync_pb::EntityMetadata>( + is_not_deletion_metadata)); return batch; }); @@ -935,25 +948,35 @@ TEST_F(PasswordSyncBridgeTest, ShouldNotifyUnsyncedCredentialsIfAccountStore) { // because the deletion is supposed to have already removed such form. const int kPrimaryKeyUnsyncedCredential = 1000; const int kPrimaryKeySyncedCredential = 1001; + const int kPrimaryKeyUnsyncedBlacklist = 1003; autofill::PasswordForm unsynced_credential = MakePasswordForm(kSignonRealm1); autofill::PasswordForm synced_credential = MakePasswordForm(kSignonRealm2); + autofill::PasswordForm unsynced_blacklist = + MakeBlacklistedForm(kSignonRealm3); fake_db()->AddLoginForPrimaryKey(kPrimaryKeyUnsyncedCredential, unsynced_credential); fake_db()->AddLoginForPrimaryKey(kPrimaryKeySyncedCredential, synced_credential); + fake_db()->AddLoginForPrimaryKey(kPrimaryKeyUnsyncedBlacklist, + unsynced_blacklist); // The notification should only contain new credentials that are unsynced, - // ignoring both synced ones and deletion entries. + // ignoring both synced ones, deletion entries and blacklists. EXPECT_CALL(*mock_password_store_sync(), NotifyUnsyncedCredentialsWillBeDeleted( UnorderedElementsAre(unsynced_credential))); // The content of the metadata change list does not matter in this case. bridge()->ApplyStopSyncChanges(bridge()->CreateMetadataChangeList()); + + histogram_tester.ExpectUniqueSample( + "PasswordManager.AccountStorage.UnsyncedPasswordsFoundDuringSignOut", 1, + 1); } TEST_F(PasswordSyncBridgeTest, ShouldNotNotifyUnsyncedCredentialsIfProfileStore) { + base::HistogramTester histogram_tester; ON_CALL(*mock_password_store_sync(), IsAccountStore()) .WillByDefault(Return(false)); @@ -983,6 +1006,9 @@ TEST_F(PasswordSyncBridgeTest, // The content of the metadata change list does not matter in this case. bridge()->ApplyStopSyncChanges(bridge()->CreateMetadataChangeList()); + + histogram_tester.ExpectTotalCount( + "PasswordManager.AccountStorage.UnsyncedPasswordsFoundDuringSignOut", 0); } } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/sync_credentials_filter.cc b/chromium/components/password_manager/core/browser/sync_credentials_filter.cc index a453c4b1bcd..810192ae78a 100644 --- a/chromium/components/password_manager/core/browser/sync_credentials_filter.cc +++ b/chromium/components/password_manager/core/browser/sync_credentials_filter.cc @@ -28,7 +28,7 @@ SyncCredentialsFilter::SyncCredentialsFilter( sync_service_factory_function_(std::move(sync_service_factory_function)) { } -SyncCredentialsFilter::~SyncCredentialsFilter() {} +SyncCredentialsFilter::~SyncCredentialsFilter() = default; bool SyncCredentialsFilter::ShouldSave( const autofill::PasswordForm& form) const { diff --git a/chromium/components/password_manager/core/browser/sync_credentials_filter_unittest.cc b/chromium/components/password_manager/core/browser/sync_credentials_filter_unittest.cc index 62b66d69511..36e770f14a6 100644 --- a/chromium/components/password_manager/core/browser/sync_credentials_filter_unittest.cc +++ b/chromium/components/password_manager/core/browser/sync_credentials_filter_unittest.cc @@ -68,8 +68,8 @@ class FakePasswordManagerClient : public StubPasswordManagerClient { } // PasswordManagerClient: - const GURL& GetLastCommittedEntryURL() const override { - return last_committed_entry_url_; + url::Origin GetLastCommittedOrigin() const override { + return last_committed_origin_; } MockPasswordStore* GetProfilePasswordStore() const override { return password_store_.get(); @@ -78,8 +78,8 @@ class FakePasswordManagerClient : public StubPasswordManagerClient { return identity_manager_; } - void set_last_committed_entry_url(const char* url_spec) { - last_committed_entry_url_ = GURL(url_spec); + void set_last_committed_entry_url(base::StringPiece url_spec) { + last_committed_origin_ = url::Origin::Create(GURL(url_spec)); } #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED) @@ -91,7 +91,7 @@ class FakePasswordManagerClient : public StubPasswordManagerClient { void SetIsIncognito(bool is_incognito) { is_incognito_ = is_incognito; } private: - GURL last_committed_entry_url_; + url::Origin last_committed_origin_; scoped_refptr<testing::NiceMock<MockPasswordStore>> password_store_ = new testing::NiceMock<MockPasswordStore>; bool is_incognito_ = false; diff --git a/chromium/components/password_manager/core/browser/sync_username_test_base.cc b/chromium/components/password_manager/core/browser/sync_username_test_base.cc index d23c5dafa7e..e9ed4042707 100644 --- a/chromium/components/password_manager/core/browser/sync_username_test_base.cc +++ b/chromium/components/password_manager/core/browser/sync_username_test_base.cc @@ -76,7 +76,7 @@ PasswordForm SyncUsernameTestBase::SimpleNonGaiaForm(const char* username, PasswordForm form; form.signon_realm = "https://site.com"; form.username_value = ASCIIToUTF16(username); - form.origin = GURL(origin); + form.url = GURL(origin); form.form_data = CreateSigninFormData(GURL(form.signon_realm), username); return form; } diff --git a/chromium/components/password_manager/core/browser/sync_username_test_base.h b/chromium/components/password_manager/core/browser/sync_username_test_base.h index 9f70d48fb86..5ac1fbb1b56 100644 --- a/chromium/components/password_manager/core/browser/sync_username_test_base.h +++ b/chromium/components/password_manager/core/browser/sync_username_test_base.h @@ -42,8 +42,12 @@ class SyncUsernameTestBase : public testing::Test { return identity_test_env_.identity_manager(); } + void FastForwardBy(base::TimeDelta delta) { task_env_.FastForwardBy(delta); } + void RunUntilIdle() { task_env_.RunUntilIdle(); } + private: - base::test::TaskEnvironment scoped_task_env_; + base::test::TaskEnvironment task_env_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; signin::IdentityTestEnvironment identity_test_env_; syncer::TestSyncService sync_service_; }; diff --git a/chromium/components/password_manager/core/browser/test_password_store.cc b/chromium/components/password_manager/core/browser/test_password_store.cc index ac84adb0b6d..2ee5b021670 100644 --- a/chromium/components/password_manager/core/browser/test_password_store.cc +++ b/chromium/components/password_manager/core/browser/test_password_store.cc @@ -42,6 +42,9 @@ class TestPasswordSyncMetadataStore : public PasswordStoreSync::MetadataStore { 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_; @@ -60,7 +63,7 @@ bool TestPasswordSyncMetadataStore::UpdateSyncMetadata( bool TestPasswordSyncMetadataStore::ClearSyncMetadata( syncer::ModelType model_type, const std::string& storage_key) { - sync_metadata_.clear(); + sync_metadata_.erase(storage_key); return true; } @@ -96,6 +99,15 @@ void TestPasswordSyncMetadataStore::DeleteAllSyncMetadata() { sync_metadata_.clear(); } +void TestPasswordSyncMetadataStore::SetDeletionsHaveSyncedCallback( + base::RepeatingCallback<void(bool)> callback) { + NOTIMPLEMENTED(); +} + +bool TestPasswordSyncMetadataStore::HasUnsyncedDeletions() { + return false; +} + } // namespace TestPasswordStore::TestPasswordStore(bool is_account_store) @@ -170,12 +182,12 @@ PasswordStoreChangeList TestPasswordStore::UpdateLoginImpl( PasswordStoreChangeList changes; std::vector<autofill::PasswordForm>& forms = stored_passwords_[form.signon_realm]; - for (auto it = forms.begin(); it != forms.end(); ++it) { - if (ArePasswordFormUniqueKeysEqual(form, *it)) { - *it = form; - it->in_store = is_account_store_ - ? autofill::PasswordForm::Store::kAccountStore - : autofill::PasswordForm::Store::kProfileStore; + for (auto& stored_form : forms) { + if (ArePasswordFormUniqueKeysEqual(form, stored_form)) { + stored_form = form; + stored_form.in_store = is_account_store_ + ? autofill::PasswordForm::Store::kAccountStore + : autofill::PasswordForm::Store::kProfileStore; changes.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form)); } } @@ -211,15 +223,15 @@ TestPasswordStore::FillMatchingLogins(const FormDigest& form) { IsPublicSuffixDomainMatch(elements.first, form.signon_realm); if (realm_matches || realm_psl_matches || (form.scheme == autofill::PasswordForm::Scheme::kHtml && - password_manager::IsFederatedRealm(elements.first, form.origin))) { + 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 == autofill::PasswordForm::Scheme::kHtml && - stored_form.origin.GetOrigin() == form.origin.GetOrigin() && + stored_form.url.GetOrigin() == form.url.GetOrigin() && password_manager::IsFederatedRealm(stored_form.signon_realm, - form.origin))) { + form.url))) { matched_forms.push_back( std::make_unique<autofill::PasswordForm>(stored_form)); matched_forms.back()->is_public_suffix_match = is_psl; diff --git a/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.cc b/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.cc index cf7fd78a5f8..33121fcfaa5 100644 --- a/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.cc +++ b/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.cc @@ -21,7 +21,7 @@ namespace password_manager { BulkLeakCheckServiceAdapter::BulkLeakCheckServiceAdapter( SavedPasswordsPresenter* presenter, - BulkLeakCheckService* service, + BulkLeakCheckServiceInterface* service, PrefService* prefs) : presenter_(presenter), service_(service), prefs_(prefs) { DCHECK(presenter_); @@ -37,7 +37,7 @@ BulkLeakCheckServiceAdapter::~BulkLeakCheckServiceAdapter() { bool BulkLeakCheckServiceAdapter::StartBulkLeakCheck( const void* key, LeakCheckCredential::Data* data) { - if (service_->state() == BulkLeakCheckService::State::kRunning) + if (service_->GetState() == BulkLeakCheckServiceInterface::State::kRunning) return false; // Even though the BulkLeakCheckService performs canonicalization eventually @@ -69,9 +69,9 @@ void BulkLeakCheckServiceAdapter::StopBulkLeakCheck() { service_->Cancel(); } -BulkLeakCheckService::State BulkLeakCheckServiceAdapter::GetBulkLeakCheckState() - const { - return service_->state(); +BulkLeakCheckServiceInterface::State +BulkLeakCheckServiceAdapter::GetBulkLeakCheckState() const { + return service_->GetState(); } size_t BulkLeakCheckServiceAdapter::GetPendingChecksCount() const { diff --git a/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.h b/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.h index 23f0d8fbd6c..29caec471c1 100644 --- a/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.h +++ b/chromium/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter.h @@ -5,7 +5,7 @@ #ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_BULK_LEAK_CHECK_SERVICE_ADAPTER_H_ #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_BULK_LEAK_CHECK_SERVICE_ADAPTER_H_ -#include "components/password_manager/core/browser/bulk_leak_check_service.h" +#include "components/password_manager/core/browser/bulk_leak_check_service_interface.h" #include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h" #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" @@ -22,7 +22,7 @@ namespace password_manager { class BulkLeakCheckServiceAdapter : public SavedPasswordsPresenter::Observer { public: BulkLeakCheckServiceAdapter(SavedPasswordsPresenter* presenter, - BulkLeakCheckService* service, + BulkLeakCheckServiceInterface* service, PrefService* prefs); ~BulkLeakCheckServiceAdapter() override; @@ -39,7 +39,7 @@ class BulkLeakCheckServiceAdapter : public SavedPasswordsPresenter::Observer { void StopBulkLeakCheck(); // Obtains the state of the bulk leak check. - BulkLeakCheckService::State GetBulkLeakCheckState() const; + BulkLeakCheckServiceInterface::State GetBulkLeakCheckState() const; // Gets the list of pending checks. size_t GetPendingChecksCount() const; @@ -51,7 +51,7 @@ class BulkLeakCheckServiceAdapter : public SavedPasswordsPresenter::Observer { // Weak handles to a presenter and service, respectively. These must be not // null and must outlive the adapter. SavedPasswordsPresenter* presenter_ = nullptr; - BulkLeakCheckService* service_ = nullptr; + BulkLeakCheckServiceInterface* service_ = nullptr; PrefService* prefs_ = nullptr; }; diff --git a/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager.cc b/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager.cc new file mode 100644 index 00000000000..e2d92058c1c --- /dev/null +++ b/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager.cc @@ -0,0 +1,269 @@ +// Copyright 2020 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/ui/compromised_credentials_manager.h" + +#include <algorithm> +#include <iterator> +#include <set> + +#include "base/containers/flat_set.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/common/password_form.h" +#include "components/password_manager/core/browser/compromised_credentials_table.h" +#include "components/password_manager/core/browser/ui/credential_utils.h" +#include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" + +namespace password_manager { + +// Extra information about CompromisedCredentials which is required by UI. +struct CredentialMetadata { + std::vector<autofill::PasswordForm> forms; + CompromiseTypeFlags type = CompromiseTypeFlags::kNotCompromised; + base::Time latest_time; +}; + +namespace { + +using CredentialPasswordsMap = + std::map<CredentialView, CredentialMetadata, PasswordCredentialLess>; + +// Transparent comparator that can compare CompromisedCredentials and +// autofill::PasswordForm. +struct CredentialWithoutPasswordLess { + static std::tuple<const std::string&, const base::string16&> + CredentialOriginAndUsername(const autofill::PasswordForm& form) { + return std::tie(form.signon_realm, form.username_value); + } + + static std::tuple<const std::string&, const base::string16&> + CredentialOriginAndUsername(const CompromisedCredentials& c) { + return std::tie(c.signon_realm, c.username); + } + + template <typename T, typename U> + bool operator()(const T& lhs, const U& rhs) const { + return CredentialOriginAndUsername(lhs) < CredentialOriginAndUsername(rhs); + } + + using is_transparent = void; +}; + +CompromiseTypeFlags ConvertCompromiseType(CompromiseType type) { + switch (type) { + case CompromiseType::kLeaked: + return CompromiseTypeFlags::kCredentialLeaked; + case CompromiseType::kPhished: + return CompromiseTypeFlags::kCredentialPhished; + } + NOTREACHED(); +} + +// This function takes two lists of compromised credentials and saved passwords +// and joins them, producing a map that contains CredentialWithPassword as keys +// and vector<autofill::PasswordForm> as values with CredentialCompromiseType as +// values. +CredentialPasswordsMap JoinCompromisedCredentialsWithSavedPasswords( + const std::vector<CompromisedCredentials>& credentials, + SavedPasswordsPresenter::SavedPasswordsView saved_passwords) { + CredentialPasswordsMap credentials_to_forms; + + // Since a single (signon_realm, username) pair might have multiple + // corresponding entries in saved_passwords, we are using a multiset and doing + // look-up via equal_range. In most cases the resulting |range| should have a + // size of 1, however. + std::multiset<autofill::PasswordForm, CredentialWithoutPasswordLess> + password_forms(saved_passwords.begin(), saved_passwords.end()); + for (const auto& credential : credentials) { + auto range = password_forms.equal_range(credential); + // Make use of a set to only filter out repeated passwords, if any. + std::for_each( + range.first, range.second, [&](const autofill::PasswordForm& form) { + CredentialView compromised_credential(form); + auto& credential_to_form = + credentials_to_forms[compromised_credential]; + + // Using |= operator to save in a bit mask both Leaked and Phished. + credential_to_form.type = + credential_to_form.type | + ConvertCompromiseType(credential.compromise_type); + + // Use the latest time. Relevant when the same credential is both + // phished and compromised. + credential_to_form.latest_time = + std::max(credential_to_form.latest_time, credential.create_time); + + // Populate the map. The values are vectors, because it is + // possible that multiple saved passwords match to the same + // compromised credential. + credential_to_form.forms.push_back(form); + }); + } + + return credentials_to_forms; +} + +std::vector<CredentialWithPassword> ExtractCompromisedCredentials( + const CredentialPasswordsMap& credentials_to_forms) { + std::vector<CredentialWithPassword> credentials; + credentials.reserve(credentials_to_forms.size()); + for (const auto& credential_to_forms : credentials_to_forms) { + CredentialWithPassword credential(credential_to_forms.first); + credential.compromise_type = credential_to_forms.second.type; + credential.create_time = credential_to_forms.second.latest_time; + credentials.push_back(std::move(credential)); + } + return credentials; +} + +} // namespace + +CredentialWithPassword::CredentialWithPassword(const CredentialView& credential) + : CredentialView(std::move(credential)) {} +CredentialWithPassword::~CredentialWithPassword() = default; +CredentialWithPassword::CredentialWithPassword( + const CredentialWithPassword& other) = default; + +CredentialWithPassword::CredentialWithPassword(CredentialWithPassword&& other) = + default; +CredentialWithPassword::CredentialWithPassword( + const CompromisedCredentials& credential) { + username = credential.username; + signon_realm = credential.signon_realm; + create_time = credential.create_time; + compromise_type = ConvertCompromiseType(credential.compromise_type); +} + +CredentialWithPassword& CredentialWithPassword::operator=( + const CredentialWithPassword& other) = default; +CredentialWithPassword& CredentialWithPassword::operator=( + CredentialWithPassword&& other) = default; + +CompromisedCredentialsManager::CompromisedCredentialsManager( + scoped_refptr<PasswordStore> store, + SavedPasswordsPresenter* presenter) + : store_(std::move(store)), presenter_(presenter) { + observed_password_store_.Add(store_.get()); + observed_saved_password_presenter_.Add(presenter_); +} + +CompromisedCredentialsManager::~CompromisedCredentialsManager() = default; + +void CompromisedCredentialsManager::Init() { + store_->GetAllCompromisedCredentials(this); +} + +void CompromisedCredentialsManager::SaveCompromisedCredential( + const LeakCheckCredential& credential) { + // Iterate over all currently saved credentials and mark those as compromised + // that have the same canonicalized username and password. + const base::string16 canonicalized_username = + CanonicalizeUsername(credential.username()); + for (const autofill::PasswordForm& saved_password : + presenter_->GetSavedPasswords()) { + if (saved_password.password_value == credential.password() && + CanonicalizeUsername(saved_password.username_value) == + canonicalized_username) { + store_->AddCompromisedCredentials({ + .signon_realm = saved_password.signon_realm, + .username = saved_password.username_value, + .create_time = base::Time::Now(), + .compromise_type = CompromiseType::kLeaked, + }); + } + } +} + +bool CompromisedCredentialsManager::UpdateCompromisedCredentials( + const CredentialView& credential, + const base::StringPiece password) { + auto it = credentials_to_forms_.find(credential); + if (it == credentials_to_forms_.end()) + return false; + + // Make sure there are matching password forms. Also erase duplicates if there + // are any. + const auto& forms = it->second.forms; + if (forms.empty()) + return false; + + for (size_t i = 1; i < forms.size(); ++i) + store_->RemoveLogin(forms[i]); + + // Note: We Invoke EditPassword on the presenter rather than UpdateLogin() on + // the store, so that observers of the presenter get notified of this event. + return presenter_->EditPassword(forms[0], base::UTF8ToUTF16(password)); +} + +bool CompromisedCredentialsManager::RemoveCompromisedCredential( + const CredentialView& credential) { + auto it = credentials_to_forms_.find(credential); + if (it == credentials_to_forms_.end()) + return false; + + // Erase all matching credentials from the store. Return whether any + // credentials were deleted. + const auto& saved_passwords = it->second.forms; + for (const autofill::PasswordForm& saved_password : saved_passwords) + store_->RemoveLogin(saved_password); + + return !saved_passwords.empty(); +} + +std::vector<CredentialWithPassword> +CompromisedCredentialsManager::GetCompromisedCredentials() const { + return ExtractCompromisedCredentials(credentials_to_forms_); +} + +SavedPasswordsPresenter::SavedPasswordsView +CompromisedCredentialsManager::GetSavedPasswordsFor( + const CredentialView& credential) const { + auto it = credentials_to_forms_.find(credential); + return it != credentials_to_forms_.end() + ? it->second.forms + : SavedPasswordsPresenter::SavedPasswordsView(); +} + +void CompromisedCredentialsManager::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void CompromisedCredentialsManager::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void CompromisedCredentialsManager::OnCompromisedCredentialsChanged() { + // Cancel ongoing requests to the password store and issue a new request. + cancelable_task_tracker()->TryCancelAll(); + store_->GetAllCompromisedCredentials(this); +} + +// Re-computes the list of compromised credentials with passwords after +// obtaining a new list of compromised credentials. +void CompromisedCredentialsManager::OnGetCompromisedCredentials( + std::vector<CompromisedCredentials> compromised_credentials) { + compromised_credentials_ = std::move(compromised_credentials); + UpdateCachedDataAndNotifyObservers(presenter_->GetSavedPasswords()); +} + +// Re-computes the list of compromised credentials with passwords after +// obtaining a new list of saved passwords. +void CompromisedCredentialsManager::OnSavedPasswordsChanged( + SavedPasswordsPresenter::SavedPasswordsView saved_passwords) { + UpdateCachedDataAndNotifyObservers(saved_passwords); +} + +void CompromisedCredentialsManager::UpdateCachedDataAndNotifyObservers( + SavedPasswordsPresenter::SavedPasswordsView saved_passwords) { + credentials_to_forms_ = JoinCompromisedCredentialsWithSavedPasswords( + compromised_credentials_, saved_passwords); + std::vector<CredentialWithPassword> credentials = + ExtractCompromisedCredentials(credentials_to_forms_); + for (auto& observer : observers_) { + observer.OnCompromisedCredentialsChanged(credentials); + } +} + +} // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager.h b/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager.h new file mode 100644 index 00000000000..a5578e72a84 --- /dev/null +++ b/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager.h @@ -0,0 +1,190 @@ +// Copyright 2020 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. + +#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_COMPROMISED_CREDENTIALS_MANAGER_H_ +#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_COMPROMISED_CREDENTIALS_MANAGER_H_ + +#include <map> +#include <vector> + +#include "base/containers/span.h" +#include "base/memory/scoped_refptr.h" +#include "base/observer_list.h" +#include "base/observer_list_types.h" +#include "base/scoped_observer.h" +#include "base/util/type_safety/strong_alias.h" +#include "components/password_manager/core/browser/compromised_credentials_consumer.h" +#include "components/password_manager/core/browser/compromised_credentials_table.h" +#include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h" +#include "components/password_manager/core/browser/password_store.h" +#include "components/password_manager/core/browser/ui/credential_utils.h" +#include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" +#include "url/gurl.h" + +namespace password_manager { + +class LeakCheckCredential; + +enum class CompromiseTypeFlags { + kNotCompromised = 0, + // If the credentials was leaked by a data breach. + kCredentialLeaked = 1 << 0, + // If the credentials was reused on a phishing site. + kCredentialPhished = 1 << 1, +}; + +constexpr CompromiseTypeFlags operator|(CompromiseTypeFlags lhs, + CompromiseTypeFlags rhs) { + return static_cast<CompromiseTypeFlags>(static_cast<int>(lhs) | + static_cast<int>(rhs)); +} + +// Simple struct that augments key values of CompromisedCredentials and a +// password. +struct CredentialView { + CredentialView() = default; + // Enable explicit construction from the autofill::PasswordForm + explicit CredentialView(const autofill::PasswordForm& form) + : signon_realm(form.signon_realm), + username(form.username_value), + password(form.password_value) {} + + std::string signon_realm; + base::string16 username; + base::string16 password; +}; + +// All information needed by UI to represent CompromisedCredential. It's a +// result of deduplicating CompromisedCredentials to have single entity both for +// phished and leaked credentials with latest |create_time|, and after that +// joining with autofill::PasswordForms to get passwords. +struct CredentialWithPassword : CredentialView { + explicit CredentialWithPassword(const CredentialView& credential); + explicit CredentialWithPassword(const CompromisedCredentials& credential); + + CredentialWithPassword(const CredentialWithPassword& other); + CredentialWithPassword(CredentialWithPassword&& other); + ~CredentialWithPassword(); + + CredentialWithPassword& operator=(const CredentialWithPassword& other); + CredentialWithPassword& operator=(CredentialWithPassword&& other); + base::Time create_time; + CompromiseTypeFlags compromise_type = CompromiseTypeFlags::kNotCompromised; +}; + +// Comparator that can compare CredentialView or CredentialsWithPasswords. +struct PasswordCredentialLess { + bool operator()(const CredentialView& lhs, const CredentialView& rhs) const { + return std::tie(lhs.signon_realm, lhs.username, lhs.password) < + std::tie(rhs.signon_realm, rhs.username, rhs.password); + } +}; + +// Extra information about CompromisedCredentials which is required by UI. +struct CredentialMetadata; + +// This class provides clients with saved compromised credentials and +// possibility to save new LeakedCredentials, edit/delete compromised +// credentials and match compromised credentials with corresponding +// autofill::PasswordForms. It supports an observer interface, and clients can +// register themselves to get notified about changes to the list. +class CompromisedCredentialsManager + : public PasswordStore::DatabaseCompromisedCredentialsObserver, + public CompromisedCredentialsConsumer, + public SavedPasswordsPresenter::Observer { + public: + using CredentialsView = base::span<const CredentialWithPassword>; + + // Observer interface. Clients can implement this to get notified about + // changes to the list of compromised credentials. Clients can register and + // de-register themselves, and are expected to do so before the provider gets + // out of scope. + class Observer : public base::CheckedObserver { + public: + virtual void OnCompromisedCredentialsChanged( + CredentialsView credentials) = 0; + }; + + explicit CompromisedCredentialsManager(scoped_refptr<PasswordStore> store, + SavedPasswordsPresenter* presenter); + ~CompromisedCredentialsManager() override; + + void Init(); + + // Marks all saved credentials which have same username & password as + // compromised. + void SaveCompromisedCredential(const LeakCheckCredential& credential); + + // Attempts to change the stored password of |credential| to |new_password|. + // Returns whether the change succeeded. + bool UpdateCompromisedCredentials(const CredentialView& credential, + const base::StringPiece password); + + // Attempts to remove |credential| from the password store. Returns whether + // the remove succeeded. + bool RemoveCompromisedCredential(const CredentialView& credential); + + // Returns a vector of currently compromised credentials. + std::vector<CredentialWithPassword> GetCompromisedCredentials() const; + + // Returns password forms which map to provided compromised credential. + // In most of the cases vector will have 1 element only. + SavedPasswordsPresenter::SavedPasswordsView GetSavedPasswordsFor( + const CredentialView& credential) const; + + // Allows clients and register and de-register themselves. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + private: + using CredentialPasswordsMap = + std::map<CredentialView, CredentialMetadata, PasswordCredentialLess>; + + // PasswordStore::DatabaseCompromisedCredentialsObserver: + void OnCompromisedCredentialsChanged() override; + + // CompromisedCredentialsConsumer: + void OnGetCompromisedCredentials( + std::vector<CompromisedCredentials> compromised_credentials) override; + + // SavedPasswordsPresenter::Observer: + void OnSavedPasswordsChanged( + SavedPasswordsPresenter::SavedPasswordsView passwords) override; + + // Function to update |credentials_to_forms_| and notify observers. + void UpdateCachedDataAndNotifyObservers( + SavedPasswordsPresenter::SavedPasswordsView saved_passwords); + + // The password store containing the compromised credentials. + scoped_refptr<PasswordStore> store_; + + // A weak handle to the presenter used to join the list of compromised + // credentials with saved passwords. Needs to outlive this instance. + SavedPasswordsPresenter* presenter_ = nullptr; + + // Cache of the most recently obtained compromised credentials. + std::vector<CompromisedCredentials> compromised_credentials_; + + // A map that matches CredentialView to corresponding PasswordForms, latest + // create_type and combined compromise type. + CredentialPasswordsMap credentials_to_forms_; + + // A scoped observer for |store_| to listen changes related to + // CompromisedCredentials only. + ScopedObserver<PasswordStore, + PasswordStore::DatabaseCompromisedCredentialsObserver, + &PasswordStore::AddDatabaseCompromisedCredentialsObserver, + &PasswordStore::RemoveDatabaseCompromisedCredentialsObserver> + observed_password_store_{this}; + + // A scoped observer for |presenter_|. + ScopedObserver<SavedPasswordsPresenter, SavedPasswordsPresenter::Observer> + observed_saved_password_presenter_{this}; + + base::ObserverList<Observer, /*check_empty=*/true> observers_; +}; + +} // namespace password_manager + +#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_COMPROMISED_CREDENTIALS_MANAGER_H_ diff --git a/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider_unittest.cc b/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager_unittest.cc index cc88ae687d8..114cb18d54a 100644 --- a/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider_unittest.cc +++ b/chromium/components/password_manager/core/browser/ui/compromised_credentials_manager_unittest.cc @@ -2,7 +2,7 @@ // 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/ui/compromised_credentials_provider.h" +#include "components/password_manager/core/browser/ui/compromised_credentials_manager.h" #include "base/memory/scoped_refptr.h" #include "base/strings/string_piece_forward.h" @@ -33,16 +33,16 @@ using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::IsEmpty; -struct MockCompromisedCredentialsProviderObserver - : CompromisedCredentialsProvider::Observer { +struct MockCompromisedCredentialsManagerObserver + : CompromisedCredentialsManager::Observer { MOCK_METHOD(void, OnCompromisedCredentialsChanged, - (CompromisedCredentialsProvider::CredentialsView), + (CompromisedCredentialsManager::CredentialsView), (override)); }; -using StrictMockCompromisedCredentialsProviderObserver = - ::testing::StrictMock<MockCompromisedCredentialsProviderObserver>; +using StrictMockCompromisedCredentialsManagerObserver = + ::testing::StrictMock<MockCompromisedCredentialsManagerObserver>; CompromisedCredentials MakeCompromised( base::StringPiece signon_realm, @@ -65,37 +65,74 @@ PasswordForm MakeSavedPassword(base::StringPiece signon_realm, return form; } -class CompromisedCredentialsProviderTest : public ::testing::Test { +LeakCheckCredential MakeLeakCredential(base::StringPiece username, + base::StringPiece password) { + return LeakCheckCredential(base::ASCIIToUTF16(username), + base::ASCIIToUTF16(password)); +} + +CredentialWithPassword MakeComprmisedCredential( + PasswordForm form, + CompromisedCredentials credential) { + CredentialWithPassword credential_with_password((CredentialView(form))); + credential_with_password.create_time = credential.create_time; + credential_with_password.compromise_type = + credential.compromise_type == CompromiseType::kLeaked + ? CompromiseTypeFlags::kCredentialLeaked + : CompromiseTypeFlags::kCredentialPhished; + return credential_with_password; +} + +class CompromisedCredentialsManagerTest : public ::testing::Test { protected: - CompromisedCredentialsProviderTest() { store_->Init(/*prefs=*/nullptr); } + CompromisedCredentialsManagerTest() { store_->Init(/*prefs=*/nullptr); } - ~CompromisedCredentialsProviderTest() override { + ~CompromisedCredentialsManagerTest() override { store_->ShutdownOnUIThread(); task_env_.RunUntilIdle(); } TestPasswordStore& store() { return *store_; } - CompromisedCredentialsProvider& provider() { return provider_; } + CompromisedCredentialsManager& provider() { return provider_; } void RunUntilIdle() { task_env_.RunUntilIdle(); } private: - base::test::SingleThreadTaskEnvironment task_env_; + base::test::SingleThreadTaskEnvironment task_env_{ + base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME}; scoped_refptr<TestPasswordStore> store_ = base::MakeRefCounted<TestPasswordStore>(); SavedPasswordsPresenter presenter_{store_}; - CompromisedCredentialsProvider provider_{store_, &presenter_}; + CompromisedCredentialsManager provider_{store_, &presenter_}; }; } // namespace +bool operator==(const CredentialWithPassword& lhs, + const CredentialWithPassword& rhs) { + return lhs.signon_realm == rhs.signon_realm && lhs.username == rhs.username && + lhs.create_time == rhs.create_time && + lhs.compromise_type == rhs.compromise_type && + lhs.password == rhs.password; +} + +std::ostream& operator<<(std::ostream& out, + const CredentialWithPassword& credential) { + return out << "{ signon_realm: " << credential.signon_realm + << ", username: " << credential.username + << ", create_time: " << credential.create_time + << ", compromise_type: " + << static_cast<int>(credential.compromise_type) + << ", password: " << credential.password << " }"; +} + // Tests whether adding and removing an observer works as expected. -TEST_F(CompromisedCredentialsProviderTest, +TEST_F(CompromisedCredentialsManagerTest, NotifyObserversAboutCompromisedCredentialChanges) { std::vector<CompromisedCredentials> credentials = { MakeCompromised(kExampleCom, kUsername1)}; - StrictMockCompromisedCredentialsProviderObserver observer; + StrictMockCompromisedCredentialsManagerObserver observer; provider().AddObserver(&observer); // Adding a compromised credential should notify observers. @@ -135,14 +172,14 @@ TEST_F(CompromisedCredentialsProviderTest, // Tests removing a compromised credentials by compromise type triggers an // observer works as expected. -TEST_F(CompromisedCredentialsProviderTest, +TEST_F(CompromisedCredentialsManagerTest, NotifyObserversAboutRemovingCompromisedCredentialsByCompromisedType) { CompromisedCredentials phished_credentials = MakeCompromised(kExampleCom, kUsername1, CompromiseType::kPhished); CompromisedCredentials leaked_credentials = MakeCompromised(kExampleCom, kUsername1, CompromiseType::kLeaked); - StrictMockCompromisedCredentialsProviderObserver observer; + StrictMockCompromisedCredentialsManagerObserver observer; provider().AddObserver(&observer); EXPECT_CALL(observer, OnCompromisedCredentialsChanged); store().AddCompromisedCredentials(phished_credentials); @@ -169,9 +206,9 @@ TEST_F(CompromisedCredentialsProviderTest, } // Tests whether adding and removing an observer works as expected. -TEST_F(CompromisedCredentialsProviderTest, +TEST_F(CompromisedCredentialsManagerTest, NotifyObserversAboutSavedPasswordsChanges) { - StrictMockCompromisedCredentialsProviderObserver observer; + StrictMockCompromisedCredentialsManagerObserver observer; provider().AddObserver(&observer); PasswordForm saved_password = @@ -202,7 +239,7 @@ TEST_F(CompromisedCredentialsProviderTest, // Tests that the provider is able to join a single password with a compromised // credential. -TEST_F(CompromisedCredentialsProviderTest, JoinSingleCredentials) { +TEST_F(CompromisedCredentialsManagerTest, JoinSingleCredentials) { PasswordForm password = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); CompromisedCredentials credential = MakeCompromised(kExampleCom, kUsername1); @@ -211,14 +248,15 @@ TEST_F(CompromisedCredentialsProviderTest, JoinSingleCredentials) { store().AddCompromisedCredentials(credential); RunUntilIdle(); - CredentialWithPassword expected(credential); + CredentialWithPassword expected = + MakeComprmisedCredential(password, credential); expected.password = password.password_value; EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected)); } // Tests that the provider is able to join a password with a credential that was // compromised in multiple ways. -TEST_F(CompromisedCredentialsProviderTest, JoinPhishedAndLeaked) { +TEST_F(CompromisedCredentialsManagerTest, JoinPhishedAndLeaked) { PasswordForm password = MakeSavedPassword(kExampleCom, kUsername1, kPassword1); CompromisedCredentials leaked = @@ -231,17 +269,17 @@ TEST_F(CompromisedCredentialsProviderTest, JoinPhishedAndLeaked) { store().AddCompromisedCredentials(phished); RunUntilIdle(); - CredentialWithPassword expected1(leaked); - expected1.password = password.password_value; - CredentialWithPassword expected2(phished); - expected2.password = password.password_value; - EXPECT_THAT(provider().GetCompromisedCredentials(), - ElementsAre(expected1, expected2)); + CredentialWithPassword expected = MakeComprmisedCredential(password, leaked); + expected.password = password.password_value; + expected.compromise_type = (CompromiseTypeFlags::kCredentialLeaked | + CompromiseTypeFlags::kCredentialPhished); + + EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected)); } // Tests that the provider reacts whenever the saved passwords or the // compromised credentials change. -TEST_F(CompromisedCredentialsProviderTest, ReactToChangesInBothTables) { +TEST_F(CompromisedCredentialsManagerTest, ReactToChangesInBothTables) { std::vector<PasswordForm> passwords = { MakeSavedPassword(kExampleCom, kUsername1, kPassword1), MakeSavedPassword(kExampleCom, kUsername2, kPassword2)}; @@ -250,10 +288,9 @@ TEST_F(CompromisedCredentialsProviderTest, ReactToChangesInBothTables) { MakeCompromised(kExampleCom, kUsername1), MakeCompromised(kExampleCom, kUsername2)}; - std::vector<CredentialWithPassword> expected(credentials.begin(), - credentials.end()); - expected[0].password = passwords[0].password_value; - expected[1].password = passwords[1].password_value; + std::vector<CredentialWithPassword> expected; + expected.push_back(MakeComprmisedCredential(passwords[0], credentials[0])); + expected.push_back(MakeComprmisedCredential(passwords[1], credentials[1])); store().AddLogin(passwords[0]); RunUntilIdle(); @@ -283,7 +320,7 @@ TEST_F(CompromisedCredentialsProviderTest, ReactToChangesInBothTables) { // Tests that the provider is able to join multiple passwords with compromised // credentials. -TEST_F(CompromisedCredentialsProviderTest, JoinMultipleCredentials) { +TEST_F(CompromisedCredentialsManagerTest, JoinMultipleCredentials) { std::vector<PasswordForm> passwords = { MakeSavedPassword(kExampleCom, kUsername1, kPassword1), MakeSavedPassword(kExampleCom, kUsername2, kPassword2)}; @@ -298,11 +335,10 @@ TEST_F(CompromisedCredentialsProviderTest, JoinMultipleCredentials) { store().AddCompromisedCredentials(credentials[1]); RunUntilIdle(); - CredentialWithPassword expected1(credentials[0]); - expected1.password = passwords[0].password_value; - - CredentialWithPassword expected2(credentials[1]); - expected2.password = passwords[1].password_value; + CredentialWithPassword expected1 = + MakeComprmisedCredential(passwords[0], credentials[0]); + CredentialWithPassword expected2 = + MakeComprmisedCredential(passwords[1], credentials[1]); EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected1, expected2)); @@ -310,7 +346,7 @@ TEST_F(CompromisedCredentialsProviderTest, JoinMultipleCredentials) { // Tests that joining a compromised credential with saved passwords with a // different username results in an empty list. -TEST_F(CompromisedCredentialsProviderTest, JoinWitDifferentUsername) { +TEST_F(CompromisedCredentialsManagerTest, JoinWitDifferentUsername) { std::vector<PasswordForm> passwords = { MakeSavedPassword(kExampleCom, kUsername2, kPassword1), MakeSavedPassword(kExampleCom, kUsername2, kPassword2)}; @@ -327,7 +363,7 @@ TEST_F(CompromisedCredentialsProviderTest, JoinWitDifferentUsername) { // Tests that joining a compromised credential with saved passwords with a // matching username but different signon_realm results in an empty list. -TEST_F(CompromisedCredentialsProviderTest, JoinWitDifferentSignonRealm) { +TEST_F(CompromisedCredentialsManagerTest, JoinWitDifferentSignonRealm) { std::vector<PasswordForm> passwords = { MakeSavedPassword(kExampleOrg, kUsername1, kPassword1), MakeSavedPassword(kExampleOrg, kUsername1, kPassword2)}; @@ -345,7 +381,7 @@ TEST_F(CompromisedCredentialsProviderTest, JoinWitDifferentSignonRealm) { // Tests that joining a compromised credential with multiple saved passwords for // the same signon_realm and username combination results in multiple entries // when the passwords are distinct. -TEST_F(CompromisedCredentialsProviderTest, JoinWithMultipleDistinctPasswords) { +TEST_F(CompromisedCredentialsManagerTest, JoinWithMultipleDistinctPasswords) { std::vector<PasswordForm> passwords = { MakeSavedPassword(kExampleCom, kUsername1, kPassword1, "element_1"), MakeSavedPassword(kExampleCom, kUsername1, kPassword2, "element_2")}; @@ -357,11 +393,10 @@ TEST_F(CompromisedCredentialsProviderTest, JoinWithMultipleDistinctPasswords) { store().AddCompromisedCredentials(credential); RunUntilIdle(); - CredentialWithPassword expected1(credential); - expected1.password = passwords[0].password_value; - - CredentialWithPassword expected2(credential); - expected2.password = passwords[1].password_value; + CredentialWithPassword expected1 = + MakeComprmisedCredential(passwords[0], credential); + CredentialWithPassword expected2 = + MakeComprmisedCredential(passwords[1], credential); EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected1, expected2)); @@ -370,7 +405,7 @@ TEST_F(CompromisedCredentialsProviderTest, JoinWithMultipleDistinctPasswords) { // Tests that joining a compromised credential with multiple saved passwords for // the same signon_realm and username combination results in a single entry // when the passwords are the same. -TEST_F(CompromisedCredentialsProviderTest, JoinWithMultipleRepeatedPasswords) { +TEST_F(CompromisedCredentialsManagerTest, JoinWithMultipleRepeatedPasswords) { CompromisedCredentials credential = MakeCompromised(kExampleCom, kUsername1); std::vector<PasswordForm> passwords = { MakeSavedPassword(kExampleCom, kUsername1, kPassword1, "element_1"), @@ -381,10 +416,110 @@ TEST_F(CompromisedCredentialsProviderTest, JoinWithMultipleRepeatedPasswords) { store().AddCompromisedCredentials(credential); RunUntilIdle(); - CredentialWithPassword expected(credential); - expected.password = passwords[0].password_value; + CredentialWithPassword expected = + MakeComprmisedCredential(passwords[0], credential); + + EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected)); +} + +// Tests that verifies mapping compromised credentials to passwords works +// correctly. +TEST_F(CompromisedCredentialsManagerTest, MapCompromisedPasswordsToPasswords) { + std::vector<PasswordForm> passwords = { + MakeSavedPassword(kExampleCom, kUsername1, kPassword1, "element_1"), + MakeSavedPassword(kExampleCom, kUsername1, kPassword1, "element_2"), + MakeSavedPassword(kExampleOrg, kUsername2, kPassword2)}; + + std::vector<CompromisedCredentials> credentials = { + MakeCompromised(kExampleCom, kUsername1), + MakeCompromised(kExampleOrg, kUsername2)}; + + std::vector<CredentialWithPassword> credentuals_with_password; + credentuals_with_password.push_back( + MakeComprmisedCredential(passwords[0], credentials[0])); + credentuals_with_password.push_back( + MakeComprmisedCredential(passwords[1], credentials[0])); + credentuals_with_password.push_back( + MakeComprmisedCredential(passwords[2], credentials[1])); + + store().AddLogin(passwords[0]); + store().AddLogin(passwords[1]); + store().AddLogin(passwords[2]); + store().AddCompromisedCredentials(credentials[0]); + store().AddCompromisedCredentials(credentials[1]); + + RunUntilIdle(); + EXPECT_THAT(provider().GetSavedPasswordsFor(credentuals_with_password[0]), + ElementsAreArray(store().stored_passwords().at(kExampleCom))); + + EXPECT_THAT(provider().GetSavedPasswordsFor(credentuals_with_password[1]), + ElementsAreArray(store().stored_passwords().at(kExampleCom))); + + EXPECT_THAT(provider().GetSavedPasswordsFor(credentuals_with_password[2]), + ElementsAreArray(store().stored_passwords().at(kExampleOrg))); +} + +// Test verifies that saving LeakCheckCredential via provider adds expected +// compromised credential. +TEST_F(CompromisedCredentialsManagerTest, SaveCompromisedPassword) { + PasswordForm password_form = + MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + LeakCheckCredential credential = MakeLeakCredential(kUsername1, kPassword1); + CompromisedCredentials compromised_credential = + MakeCompromised(kExampleCom, kUsername1); + + store().AddLogin(password_form); + RunUntilIdle(); + + CredentialWithPassword expected = + MakeComprmisedCredential(password_form, compromised_credential); + expected.create_time = base::Time::Now(); + + provider().SaveCompromisedCredential(credential); + RunUntilIdle(); + + EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected)); +} + +// Test verifies that editing Compromised Credential via provider change the +// original password form. +TEST_F(CompromisedCredentialsManagerTest, UpdateCompromisedPassword) { + PasswordForm password_form = + MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + CompromisedCredentials credential = MakeCompromised(kExampleCom, kUsername1); + + store().AddLogin(password_form); + store().AddCompromisedCredentials(credential); + + RunUntilIdle(); + CredentialWithPassword expected = + MakeComprmisedCredential(password_form, credential); + + provider().UpdateCompromisedCredentials(expected, kPassword2); + RunUntilIdle(); + expected.password = base::UTF8ToUTF16(kPassword2); EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected)); } +TEST_F(CompromisedCredentialsManagerTest, RemoveCompromisedCredential) { + CompromisedCredentials credential = MakeCompromised(kExampleCom, kUsername1); + PasswordForm password = + MakeSavedPassword(kExampleCom, kUsername1, kPassword1); + + store().AddLogin(password); + store().AddCompromisedCredentials(credential); + RunUntilIdle(); + + CredentialWithPassword expected = + MakeComprmisedCredential(password, credential); + expected.password = password.password_value; + + EXPECT_THAT(provider().GetCompromisedCredentials(), ElementsAre(expected)); + + EXPECT_TRUE(provider().RemoveCompromisedCredential(expected)); + RunUntilIdle(); + EXPECT_THAT(provider().GetCompromisedCredentials(), IsEmpty()); +} + } // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider.cc b/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider.cc deleted file mode 100644 index 9478aabec58..00000000000 --- a/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider.cc +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2020 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/ui/compromised_credentials_provider.h" - -#include <algorithm> -#include <iterator> -#include <set> - -#include "base/containers/flat_set.h" -#include "base/strings/string16.h" -#include "components/autofill/core/common/password_form.h" -#include "components/password_manager/core/browser/compromised_credentials_table.h" -#include "components/password_manager/core/browser/ui/credential_utils.h" -#include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" - -namespace password_manager { - -namespace { - -// Transparent comparator that can compare various types like CredentialView or -// CompromisedCredentials. -struct CredentialWithoutPassword { - template <typename T, typename U> - bool operator()(const T& lhs, const U& rhs) const { - return std::tie(lhs.signon_realm, lhs.username) < - std::tie(rhs.signon_realm, rhs.username); - } - - using is_transparent = void; -}; - -// This function takes two lists of compromised credentials and saved passwords -// and joins them, producing a new list that contains an entry for each element -// of |saved_passwords| whose signon_realm and username are also present in -// |compromised_credentials|. -std::vector<CredentialWithPassword> -JoinCompromisedCredentialsWithSavedPasswords( - base::span<const CompromisedCredentials> compromised_credentials, - SavedPasswordsPresenter::SavedPasswordsView saved_passwords) { - std::vector<CredentialWithPassword> compromised_credentials_with_passwords; - compromised_credentials_with_passwords.reserve( - compromised_credentials.size()); - - // Since a single (signon_realm, username) pair might have multiple - // corresponding entries in saved_passwords, we are using a multiset and doing - // look-up via equal_range. In most cases the resulting |range| should have a - // size of 1, however. - std::multiset<CredentialView, CredentialWithoutPassword> credentials( - saved_passwords.begin(), saved_passwords.end()); - for (const auto& compromised_credential : compromised_credentials) { - auto range = credentials.equal_range(compromised_credential); - // Make use of a set to only filter out repeated passwords, if any. - base::flat_set<base::string16> passwords; - std::for_each(range.first, range.second, [&](const CredentialView& view) { - if (passwords.insert(view.password).second) { - compromised_credentials_with_passwords.emplace_back( - compromised_credential); - compromised_credentials_with_passwords.back().password = view.password; - } - }); - } - - return compromised_credentials_with_passwords; -} - -} // namespace - -bool operator==(const CredentialWithPassword& lhs, - const CredentialWithPassword& rhs) { - // Upcast `lhs` to trigger the CompromisedCredentials operator==. - return static_cast<const CompromisedCredentials&>(lhs) == rhs && - lhs.password == rhs.password; -} - -std::ostream& operator<<(std::ostream& out, - const CredentialWithPassword& credential) { - return out << "{ signon_realm: " << credential.signon_realm - << ", username: " << credential.username - << ", create_time: " << credential.create_time - << ", compromise_type: " - << static_cast<int>(credential.compromise_type) - << ", password: " << credential.password << " }"; -} - -CompromisedCredentialsProvider::CompromisedCredentialsProvider( - scoped_refptr<PasswordStore> store, - SavedPasswordsPresenter* presenter) - : store_(std::move(store)), presenter_(presenter) { - store_->AddDatabaseCompromisedCredentialsObserver(this); - presenter_->AddObserver(this); -} - -CompromisedCredentialsProvider::~CompromisedCredentialsProvider() { - presenter_->RemoveObserver(this); - store_->RemoveDatabaseCompromisedCredentialsObserver(this); -} - -void CompromisedCredentialsProvider::Init() { - store_->GetAllCompromisedCredentials(this); -} - -CompromisedCredentialsProvider::CredentialsView -CompromisedCredentialsProvider::GetCompromisedCredentials() const { - return compromised_credentials_with_passwords_; -} - -void CompromisedCredentialsProvider::AddObserver(Observer* observer) { - observers_.AddObserver(observer); -} - -void CompromisedCredentialsProvider::RemoveObserver(Observer* observer) { - observers_.RemoveObserver(observer); -} - -void CompromisedCredentialsProvider::OnCompromisedCredentialsChanged() { - // Cancel ongoing requests to the password store and issue a new request. - cancelable_task_tracker()->TryCancelAll(); - store_->GetAllCompromisedCredentials(this); -} - -// Re-computes the list of compromised credentials with passwords after -// obtaining a new list of compromised credentials. -void CompromisedCredentialsProvider::OnGetCompromisedCredentials( - std::vector<CompromisedCredentials> compromised_credentials) { - compromised_credentials_ = std::move(compromised_credentials); - compromised_credentials_with_passwords_ = - JoinCompromisedCredentialsWithSavedPasswords( - compromised_credentials_, presenter_->GetSavedPasswords()); - NotifyCompromisedCredentialsChanged(); -} - -// Re-computes the list of compromised credentials with passwords after -// obtaining a new list of saved passwords. -void CompromisedCredentialsProvider::OnSavedPasswordsChanged( - SavedPasswordsPresenter::SavedPasswordsView saved_passwords) { - compromised_credentials_with_passwords_ = - JoinCompromisedCredentialsWithSavedPasswords(compromised_credentials_, - saved_passwords); - NotifyCompromisedCredentialsChanged(); -} - -void CompromisedCredentialsProvider::NotifyCompromisedCredentialsChanged() { - for (auto& observer : observers_) { - observer.OnCompromisedCredentialsChanged( - compromised_credentials_with_passwords_); - } -} - -} // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider.h b/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider.h deleted file mode 100644 index 53d8132f404..00000000000 --- a/chromium/components/password_manager/core/browser/ui/compromised_credentials_provider.h +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2020 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. - -#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_COMPROMISED_CREDENTIALS_PROVIDER_H_ -#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_COMPROMISED_CREDENTIALS_PROVIDER_H_ - -#include "base/containers/span.h" -#include "base/memory/scoped_refptr.h" -#include "base/observer_list.h" -#include "base/observer_list_types.h" -#include "base/strings/string16.h" -#include "components/password_manager/core/browser/compromised_credentials_consumer.h" -#include "components/password_manager/core/browser/compromised_credentials_table.h" -#include "components/password_manager/core/browser/password_store.h" -#include "components/password_manager/core/browser/ui/saved_passwords_presenter.h" -#include "url/gurl.h" - -namespace password_manager { - -// Simple struct that augments the CompromisedCredentials with a password. -struct CredentialWithPassword : CompromisedCredentials { - // Enable explicit construction from the parent struct. This will leave - // |password| empty. - explicit CredentialWithPassword(CompromisedCredentials credential) - : CompromisedCredentials(std::move(credential)) {} - - base::string16 password; -}; - -bool operator==(const CredentialWithPassword& lhs, - const CredentialWithPassword& rhs); - -std::ostream& operator<<(std::ostream& out, - const CredentialWithPassword& credential); - -// This class provides a read-only view over saved compromised credentials. It -// supports an observer interface, and clients can register themselves to get -// notified about changes to the list. -class CompromisedCredentialsProvider - : public PasswordStore::DatabaseCompromisedCredentialsObserver, - public CompromisedCredentialsConsumer, - public SavedPasswordsPresenter::Observer { - public: - - using CredentialsView = base::span<const CredentialWithPassword>; - - // Observer interface. Clients can implement this to get notified about - // changes to the list of compromised credentials. Clients can register and - // de-register themselves, and are expected to do so before the provider gets - // out of scope. - class Observer : public base::CheckedObserver { - public: - virtual void OnCompromisedCredentialsChanged( - CredentialsView credentials) = 0; - }; - - explicit CompromisedCredentialsProvider(scoped_refptr<PasswordStore> store, - SavedPasswordsPresenter* presenter); - ~CompromisedCredentialsProvider() override; - - void Init(); - - // Returns a read-only view over the currently compromised credentials. - CredentialsView GetCompromisedCredentials() const; - - // Allows clients and register and de-register themselves. - void AddObserver(Observer* observer); - void RemoveObserver(Observer* observer); - - private: - // PasswordStore::DatabaseCompromisedCredentialsObserver: - void OnCompromisedCredentialsChanged() override; - - // CompromisedCredentialsConsumer: - void OnGetCompromisedCredentials( - std::vector<CompromisedCredentials> compromised_credentials) override; - - // SavedPasswordsPresenter::Observer: - void OnSavedPasswordsChanged( - SavedPasswordsPresenter::SavedPasswordsView passwords) override; - - // Notify observers about changes to - // |compromised_credentials_with_passwords_|. - void NotifyCompromisedCredentialsChanged(); - - // The password store containing the compromised credentials. - scoped_refptr<PasswordStore> store_; - - // A weak handle to the presenter used to join the list of compromised - // credentials with saved passwords. Needs to outlive this instance. - SavedPasswordsPresenter* presenter_ = nullptr; - - // Cache of the most recently obtained compromised credentials. - std::vector<CompromisedCredentials> compromised_credentials_; - - // Cache of the most recently obtained compromised credentials with passwords. - std::vector<CredentialWithPassword> compromised_credentials_with_passwords_; - - base::ObserverList<Observer, /*check_empty=*/true> observers_; -}; - -} // namespace password_manager - -#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_COMPROMISED_CREDENTIALS_PROVIDER_H_ diff --git a/chromium/components/password_manager/core/browser/ui/credential_utils.h b/chromium/components/password_manager/core/browser/ui/credential_utils.h index 828d92c4a5a..b0d4240ece7 100644 --- a/chromium/components/password_manager/core/browser/ui/credential_utils.h +++ b/chromium/components/password_manager/core/browser/ui/credential_utils.h @@ -18,19 +18,6 @@ namespace password_manager { -// A view over a PasswordForm that only stores the signon_realm, username and -// password. An implicit constructor is provided for convenience. -struct CredentialView { - CredentialView(const autofill::PasswordForm& form) - : signon_realm(form.signon_realm), - username(form.username_value), - password(form.password_value) {} - - std::string signon_realm; - base::string16 username; - base::string16 password; -}; - // Simple struct that stores a canonicalized credential. Allows implicit // constructon from PasswordForm and LeakCheckCredentail for convenience. struct CanonicalizedCredential { @@ -52,18 +39,6 @@ inline bool operator<(const CanonicalizedCredential& lhs, std::tie(rhs.canonicalized_username, rhs.password); } -// Transparent comparator that can compare various types like CredentialView or -// CredentialsWithPasswords. -struct PasswordCredentialLess { - template <typename T, typename U> - bool operator()(const T& lhs, const U& rhs) const { - return std::tie(lhs.signon_realm, lhs.username, lhs.password) < - std::tie(rhs.signon_realm, rhs.username, rhs.password); - } - - using is_transparent = void; -}; - } // namespace password_manager #endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_CREDENTIAL_UTILS_H_ diff --git a/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper.cc b/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper.cc new file mode 100644 index 00000000000..1804ec99f5f --- /dev/null +++ b/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper.cc @@ -0,0 +1,66 @@ +// Copyright 2020 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/ui/post_save_compromised_helper.h" + +#include "base/stl_util.h" +#include "components/password_manager/core/browser/password_store.h" +#include "components/password_manager/core/common/password_manager_pref_names.h" +#include "components/prefs/pref_service.h" + +namespace password_manager { + +// Maximum time since the last password check while the result is considered +// up to date. +constexpr auto kMaxTimeSinceLastCheck = base::TimeDelta::FromMinutes(30); + +PostSaveCompromisedHelper::PostSaveCompromisedHelper( + base::span<const CompromisedCredentials> compromised, + const base::string16& current_username) { + for (const CompromisedCredentials& credential : compromised) { + if (credential.username == current_username) + current_leak_ = credential; + } +} + +PostSaveCompromisedHelper::~PostSaveCompromisedHelper() = default; + +void PostSaveCompromisedHelper::AnalyzeLeakedCredentials( + PasswordStore* store, + PrefService* prefs, + BubbleCallback callback) { + DCHECK(store); + DCHECK(prefs); + callback_ = std::move(callback); + prefs_ = prefs; + store->GetAllCompromisedCredentials(this); +} + +void PostSaveCompromisedHelper::OnGetCompromisedCredentials( + std::vector<CompromisedCredentials> compromised_credentials) { + const bool compromised_password_changed = + current_leak_ && !base::Contains(compromised_credentials, *current_leak_); + bubble_type_ = BubbleType::kNoBubble; + if (compromised_credentials.empty()) { + if (compromised_password_changed) { + // Obtain the timestamp of the last completed check. This is 0.0 in case + // the check never completely ran before. + const double last_check_completed = + prefs_->GetDouble(prefs::kLastTimePasswordCheckCompleted); + if (last_check_completed && + base::Time::Now() - base::Time::FromDoubleT(last_check_completed) < + kMaxTimeSinceLastCheck) { + bubble_type_ = BubbleType::kPasswordUpdatedSafeState; + } + } + } else { + bubble_type_ = compromised_password_changed + ? BubbleType::kPasswordUpdatedWithMoreToFix + : BubbleType::kUnsafeState; + } + compromised_count_ = compromised_credentials.size(); + std::move(callback_).Run(bubble_type_, compromised_count_); +} + +} // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper.h b/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper.h new file mode 100644 index 00000000000..bdd9d4ded7c --- /dev/null +++ b/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper.h @@ -0,0 +1,80 @@ +// Copyright 2020 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. + +#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_POST_SAVE_COMPROMISED_HELPER_H_ +#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_POST_SAVE_COMPROMISED_HELPER_H_ + +#include "base/callback.h" +#include "base/containers/span.h" +#include "base/optional.h" +#include "base/strings/string16.h" +#include "base/time/time.h" +#include "components/password_manager/core/browser/compromised_credentials_consumer.h" +#include "components/password_manager/core/browser/compromised_credentials_table.h" + +class PrefService; + +namespace password_manager { + +class PasswordStore; + +// Helps to choose a compromised credential bubble after a password was saved. +class PostSaveCompromisedHelper : public CompromisedCredentialsConsumer { + public: + enum class BubbleType { + // No follow-up bubble should be shown. + kNoBubble, + // Last compromised password was updated and the password check completed + // recently. The user is presumed safe. + kPasswordUpdatedSafeState, + // A compromised password was updated and there are more issues to fix. + kPasswordUpdatedWithMoreToFix, + // A random password was saved/updated. There are stored compromised + // credentials. + kUnsafeState, + }; + + // The callback is told which bubble to bring up and how many compromised + // credentials in total should be still fixed. + using BubbleCallback = base::OnceCallback<void(BubbleType, size_t)>; + + // |compromised| contains all compromised credentials for the current site. + // |current_username| is the username that was just saved or updated. + PostSaveCompromisedHelper( + base::span<const CompromisedCredentials> compromised, + const base::string16& current_username); + ~PostSaveCompromisedHelper() override; + + PostSaveCompromisedHelper(const PostSaveCompromisedHelper&) = delete; + PostSaveCompromisedHelper& operator=(const PostSaveCompromisedHelper&) = + delete; + + // Asynchronously queries the password store for the compromised credentials + // and notifies |callback| with the result of analysis. + void AnalyzeLeakedCredentials(PasswordStore* store, + PrefService* prefs, + BubbleCallback callback); + + BubbleType bubble_type() const { return bubble_type_; } + size_t compromised_count() const { return compromised_count_; } + + private: + void OnGetCompromisedCredentials( + std::vector<CompromisedCredentials> compromised_credentials) override; + + // Contains the entry for the currently leaked credentials if it was leaked. + base::Optional<CompromisedCredentials> current_leak_; + // Profile prefs. + PrefService* prefs_ = nullptr; + // Callback to notify the caller about the bubble type. + BubbleCallback callback_; + // BubbleType after the callback was executed. + BubbleType bubble_type_ = BubbleType::kNoBubble; + // Count of compromised credentials after the callback was executed. + size_t compromised_count_ = 0; +}; + +} // namespace password_manager + +#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_UI_POST_SAVE_COMPROMISED_HELPER_H_ diff --git a/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper_unittest.cc b/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper_unittest.cc new file mode 100644 index 00000000000..80bff25b570 --- /dev/null +++ b/chromium/components/password_manager/core/browser/ui/post_save_compromised_helper_unittest.cc @@ -0,0 +1,169 @@ +// Copyright 2020 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/ui/post_save_compromised_helper.h" + +#include "base/strings/utf_string_conversions.h" +#include "base/test/mock_callback.h" +#include "base/test/task_environment.h" +#include "components/password_manager/core/browser/mock_password_store.h" +#include "components/password_manager/core/common/password_manager_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace password_manager { +namespace { + +using BubbleType = PostSaveCompromisedHelper::BubbleType; +using prefs::kLastTimePasswordCheckCompleted; +using testing::Return; + +constexpr char kSignonRealm[] = "https://example.com/"; +constexpr char kUsername[] = "user"; +constexpr char kUsername2[] = "user2"; + +CompromisedCredentials CreateCompromised(base::StringPiece username) { + return CompromisedCredentials{ + .signon_realm = kSignonRealm, + .username = base::ASCIIToUTF16(username), + .compromise_type = CompromiseType::kLeaked, + }; +} + +} // namespace + +class PostSaveCompromisedHelperTest : public testing::Test { + public: + PostSaveCompromisedHelperTest() { + mock_store_ = new MockPasswordStore; + EXPECT_TRUE(mock_store_->Init(&prefs_)); + prefs_.registry()->RegisterDoublePref(kLastTimePasswordCheckCompleted, 0.0); + } + + ~PostSaveCompromisedHelperTest() override { + mock_store_->ShutdownOnUIThread(); + } + + void WaitForPasswordStore() { task_environment_.RunUntilIdle(); } + + MockPasswordStore* store() { return mock_store_.get(); } + TestingPrefServiceSimple* prefs() { return &prefs_; } + + private: + base::test::SingleThreadTaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + scoped_refptr<MockPasswordStore> mock_store_; + TestingPrefServiceSimple prefs_; +}; + +TEST_F(PostSaveCompromisedHelperTest, DefaultState) { + PostSaveCompromisedHelper helper({}, base::ASCIIToUTF16(kUsername)); + EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type()); + EXPECT_EQ(0u, helper.compromised_count()); +} + +TEST_F(PostSaveCompromisedHelperTest, EmptyStore) { + PostSaveCompromisedHelper helper({}, base::ASCIIToUTF16(kUsername)); + base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback; + EXPECT_CALL(callback, Run(BubbleType::kNoBubble, 0)); + EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl); + helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get()); + WaitForPasswordStore(); + EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type()); + EXPECT_EQ(0u, helper.compromised_count()); +} + +TEST_F(PostSaveCompromisedHelperTest, RandomSite_FullStore) { + PostSaveCompromisedHelper helper({}, base::ASCIIToUTF16(kUsername)); + base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback; + EXPECT_CALL(callback, Run(BubbleType::kUnsafeState, 1)); + std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername2)}; + EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl) + .WillOnce(Return(saved)); + helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get()); + WaitForPasswordStore(); + EXPECT_EQ(BubbleType::kUnsafeState, helper.bubble_type()); + EXPECT_EQ(1u, helper.compromised_count()); +} + +TEST_F(PostSaveCompromisedHelperTest, CompromisedSite_ItemStayed) { + std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername), + CreateCompromised(kUsername2)}; + PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername)); + base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback; + EXPECT_CALL(callback, Run(BubbleType::kUnsafeState, 2)); + EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl) + .WillOnce(Return(saved)); + helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get()); + WaitForPasswordStore(); + EXPECT_EQ(BubbleType::kUnsafeState, helper.bubble_type()); + EXPECT_EQ(2u, helper.compromised_count()); +} + +TEST_F(PostSaveCompromisedHelperTest, CompromisedSite_ItemGone) { + std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername), + CreateCompromised(kUsername2)}; + PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername)); + base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback; + EXPECT_CALL(callback, Run(BubbleType::kPasswordUpdatedWithMoreToFix, 1)); + saved = {CreateCompromised(kUsername2)}; + EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl) + .WillOnce(Return(saved)); + helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get()); + WaitForPasswordStore(); + EXPECT_EQ(BubbleType::kPasswordUpdatedWithMoreToFix, helper.bubble_type()); + EXPECT_EQ(1u, helper.compromised_count()); +} + +TEST_F(PostSaveCompromisedHelperTest, FixedLast_BulkCheckNeverDone) { + std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername)}; + PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername)); + base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback; + EXPECT_CALL(callback, Run(BubbleType::kNoBubble, 0)); + saved = {}; + EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl) + .WillOnce(Return(saved)); + helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get()); + WaitForPasswordStore(); + EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type()); + EXPECT_EQ(0u, helper.compromised_count()); +} + +TEST_F(PostSaveCompromisedHelperTest, FixedLast_BulkCheckDoneLongAgo) { + prefs()->SetDouble( + kLastTimePasswordCheckCompleted, + (base::Time::Now() - base::TimeDelta::FromDays(5)).ToDoubleT()); + std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername)}; + PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername)); + base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback; + EXPECT_CALL(callback, Run(BubbleType::kNoBubble, 0)); + saved = {}; + EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl) + .WillOnce(Return(saved)); + helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get()); + WaitForPasswordStore(); + EXPECT_EQ(BubbleType::kNoBubble, helper.bubble_type()); + EXPECT_EQ(0u, helper.compromised_count()); +} + +TEST_F(PostSaveCompromisedHelperTest, FixedLast_BulkCheckDoneRecently) { + prefs()->SetDouble( + kLastTimePasswordCheckCompleted, + (base::Time::Now() - base::TimeDelta::FromMinutes(1)).ToDoubleT()); + std::vector<CompromisedCredentials> saved = {CreateCompromised(kUsername)}; + PostSaveCompromisedHelper helper({saved}, base::ASCIIToUTF16(kUsername)); + base::MockCallback<PostSaveCompromisedHelper::BubbleCallback> callback; + EXPECT_CALL(callback, Run(BubbleType::kPasswordUpdatedSafeState, 0)); + saved = {}; + EXPECT_CALL(*store(), GetAllCompromisedCredentialsImpl) + .WillOnce(Return(saved)); + helper.AnalyzeLeakedCredentials(store(), prefs(), callback.Get()); + WaitForPasswordStore(); + EXPECT_EQ(BubbleType::kPasswordUpdatedSafeState, helper.bubble_type()); + EXPECT_EQ(0u, helper.compromised_count()); +} + +} // namespace password_manager diff --git a/chromium/components/password_manager/core/browser/votes_uploader.cc b/chromium/components/password_manager/core/browser/votes_uploader.cc index 6ef392a67ae..622c8a6751f 100644 --- a/chromium/components/password_manager/core/browser/votes_uploader.cc +++ b/chromium/components/password_manager/core/browser/votes_uploader.cc @@ -523,7 +523,7 @@ void VotesUploader::AddGeneratedVote(FormStructure* form_structure) { DCHECK(form_structure); DCHECK(generation_popup_was_shown_); - if (generation_element_.empty()) + if (!generation_element_) return; AutofillUploadContents::Field::PasswordGenerationType type = @@ -551,7 +551,7 @@ void VotesUploader::AddGeneratedVote(FormStructure* form_structure) { for (size_t i = 0; i < form_structure->field_count(); ++i) { AutofillField* field = form_structure->field(i); - if (field->name == generation_element_) { + if (field->unique_renderer_id == generation_element_) { field->set_generation_type(type); if (has_generated_password_) { field->set_generated_password_changed(generated_password_changed_); diff --git a/chromium/components/password_manager/core/browser/votes_uploader.h b/chromium/components/password_manager/core/browser/votes_uploader.h index bac1c279848..ef5bc256dac 100644 --- a/chromium/components/password_manager/core/browser/votes_uploader.h +++ b/chromium/components/password_manager/core/browser/votes_uploader.h @@ -130,11 +130,11 @@ class VotesUploader { is_manual_generation_ = is_manual_generation; } - const base::string16& get_generation_element() const { + autofill::FieldRendererId get_generation_element() const { return generation_element_; } - void set_generation_element(const base::string16& generation_element) { + void set_generation_element(autofill::FieldRendererId generation_element) { generation_element_ = generation_element; } @@ -213,8 +213,7 @@ class VotesUploader { bool is_manual_generation_ = false; // A password field name that is used for generation. - // TODO(crbug.com/1075444): Use unique renderer id of a field instead. - base::string16 generation_element_; + autofill::FieldRendererId generation_element_; // True iff a user edited the username value in a prompt and new username is // the value of another field of the observed form. diff --git a/chromium/components/password_manager/core/browser/votes_uploader_unittest.cc b/chromium/components/password_manager/core/browser/votes_uploader_unittest.cc index ab1c10bbd1a..d8cb59ea3dc 100644 --- a/chromium/components/password_manager/core/browser/votes_uploader_unittest.cc +++ b/chromium/components/password_manager/core/browser/votes_uploader_unittest.cc @@ -82,7 +82,7 @@ class MockAutofillDownloadManager : public AutofillDownloadManager { class StubObserver : public AutofillDownloadManager::Observer { void OnLoadedServerPredictions( std::string response, - const std::vector<std::string>& form_signatures) override {} + const autofill::FormAndFieldSignatures& form_signatures) override {} }; StubObserver fake_observer; diff --git a/chromium/components/password_manager/core/common/credential_manager_types.cc b/chromium/components/password_manager/core/common/credential_manager_types.cc index 6d90c1e7dee..ff5a5da4c0f 100644 --- a/chromium/components/password_manager/core/common/credential_manager_types.cc +++ b/chromium/components/password_manager/core/common/credential_manager_types.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/common/credential_manager_types.h" +#include <memory> + #include "base/strings/string_number_conversions.h" #include "components/autofill/core/common/password_form.h" @@ -53,8 +55,7 @@ CredentialInfo::CredentialInfo(const autofill::PasswordForm& form, CredentialInfo::CredentialInfo(const CredentialInfo& other) = default; -CredentialInfo::~CredentialInfo() { -} +CredentialInfo::~CredentialInfo() = default; bool CredentialInfo::operator==(const CredentialInfo& rhs) const { return (type == rhs.type && id == rhs.id && name == rhs.name && @@ -64,16 +65,16 @@ bool CredentialInfo::operator==(const CredentialInfo& rhs) const { std::unique_ptr<autofill::PasswordForm> CreatePasswordFormFromCredentialInfo( const CredentialInfo& info, - const GURL& origin) { + const url::Origin& origin) { std::unique_ptr<autofill::PasswordForm> form; if (info.type == CredentialType::CREDENTIAL_TYPE_EMPTY) return form; - form.reset(new autofill::PasswordForm); + form = std::make_unique<autofill::PasswordForm>(); form->icon_url = info.icon; form->display_name = info.name.value_or(base::string16()); form->federation_origin = info.federation; - form->origin = origin; + form->url = origin.GetURL(); form->password_value = info.password.value_or(base::string16()); form->username_value = info.id.value_or(base::string16()); form->scheme = autofill::PasswordForm::Scheme::kHtml; @@ -81,7 +82,7 @@ std::unique_ptr<autofill::PasswordForm> CreatePasswordFormFromCredentialInfo( form->signon_realm = info.type == CredentialType::CREDENTIAL_TYPE_PASSWORD - ? origin.GetOrigin().spec() + ? form->url.spec() : "federation://" + origin.host() + "/" + info.federation.host(); return form; } diff --git a/chromium/components/password_manager/core/common/credential_manager_types.h b/chromium/components/password_manager/core/common/credential_manager_types.h index b50567f6592..b4fd4e95962 100644 --- a/chromium/components/password_manager/core/common/credential_manager_types.h +++ b/chromium/components/password_manager/core/common/credential_manager_types.h @@ -80,7 +80,7 @@ struct CredentialInfo { // CREDENTIAL_TYPE_EMPTY. std::unique_ptr<autofill::PasswordForm> CreatePasswordFormFromCredentialInfo( const CredentialInfo& info, - const GURL& origin); + const url::Origin& origin); } // namespace password_manager diff --git a/chromium/components/password_manager/core/common/credential_manager_types_unittest.cc b/chromium/components/password_manager/core/common/credential_manager_types_unittest.cc index e0ccf39f045..169faefe3b7 100644 --- a/chromium/components/password_manager/core/common/credential_manager_types_unittest.cc +++ b/chromium/components/password_manager/core/common/credential_manager_types_unittest.cc @@ -16,12 +16,12 @@ namespace password_manager { class CredentialManagerTypesTest : public testing::Test { public: CredentialManagerTypesTest() - : origin_(GURL("https://example.test/")), + : origin_(url::Origin::Create(GURL("https://example.test/"))), icon_(GURL("https://fast-cdn.test/icon.png")), federation_(url::Origin::Create(GURL("https://federation.test/"))) {} protected: - GURL origin_; + url::Origin origin_; GURL icon_; url::Origin federation_; }; @@ -51,7 +51,7 @@ TEST_F(CredentialManagerTypesTest, CreatePasswordFormFederation) { EXPECT_EQ(autofill::PasswordForm::Type::kApi, form->type); EXPECT_EQ(info.icon, form->icon_url); EXPECT_EQ(info.name, form->display_name); - EXPECT_EQ(origin_, form->origin); + EXPECT_EQ(origin_.GetURL(), form->url); EXPECT_EQ(autofill::PasswordForm::Scheme::kHtml, form->scheme); // Federated credentials have empty passwords, non-empty federation_origins, @@ -76,14 +76,14 @@ TEST_F(CredentialManagerTypesTest, CreatePasswordFormLocal) { EXPECT_EQ(info.icon, form->icon_url); EXPECT_EQ(info.name, form->display_name); - EXPECT_EQ(origin_, form->origin); + EXPECT_EQ(origin_.GetURL().spec(), form->url); EXPECT_EQ(autofill::PasswordForm::Scheme::kHtml, form->scheme); // Local credentials have empty federation_origins, non-empty passwords, and // a signon realm that matches the origin. EXPECT_TRUE(form->federation_origin.opaque()); EXPECT_EQ(info.password, form->password_value); - EXPECT_EQ(origin_.spec(), form->signon_realm); + EXPECT_EQ(origin_.GetURL().spec(), form->signon_realm); } } // namespace password_manager diff --git a/chromium/components/password_manager/core/common/password_manager_features.cc b/chromium/components/password_manager/core/common/password_manager_features.cc index 04e63e92dcc..c85f0c83240 100644 --- a/chromium/components/password_manager/core/common/password_manager_features.cc +++ b/chromium/components/password_manager/core/common/password_manager_features.cc @@ -4,6 +4,8 @@ #include "components/password_manager/core/common/password_manager_features.h" +#include "build/build_config.h" + namespace password_manager { // NOTE: It is strongly recommended to use UpperCamelCase style for feature @@ -15,6 +17,11 @@ namespace features { const base::Feature kBiometricTouchToFill = {"BiometricTouchToFill", base::FEATURE_DISABLED_BY_DEFAULT}; +// After saving/updating a password show a bubble reminder about the status of +// other compromised credentials. +const base::Feature kCompromisedPasswordsReengagement = { + "CompromisedPasswordsReengagement", base::FEATURE_DISABLED_BY_DEFAULT}; + // Enables the editing of passwords in chrome://settings/passwords, i.e. the // Desktop passwords settings page. const base::Feature kEditPasswordsInDesktopSettings = { @@ -54,7 +61,12 @@ const base::Feature kPasswordChange = {"PasswordChange", // Enables the bulk Password Check feature for signed in users. const base::Feature kPasswordCheck = {"PasswordCheck", - base::FEATURE_DISABLED_BY_DEFAULT}; +#if defined(OS_ANDROID) || defined(OS_IOS) + base::FEATURE_DISABLED_BY_DEFAULT +#else + base::FEATURE_ENABLED_BY_DEFAULT +#endif +}; // Enables editing saved passwords for Android. const base::Feature kPasswordEditingAndroid = { @@ -78,6 +90,10 @@ const base::Feature kRecoverFromNeverSaveAndroid = { const base::Feature kUsernameFirstFlow = {"UsernameFirstFlow", base::FEATURE_DISABLED_BY_DEFAULT}; +// Enable support for .well-known/change-password URLs. +const base::Feature kWellKnownChangePassword = { + "WellKnownChangePassword", base::FEATURE_DISABLED_BY_DEFAULT}; + // Field trial identifier for password generation requirements. const char kGenerationRequirementsFieldTrial[] = "PasswordGenerationRequirements"; @@ -98,6 +114,10 @@ const char kGenerationRequirementsPrefixLength[] = "prefix_length"; // Default to 5000 ms. const char kGenerationRequirementsTimeout[] = "timeout"; +// Enables showing leaked dialog after every successful form submission. +const char kPasswordChangeWithForcedDialogAfterEverySuccessfulSubmission[] = + "should_force_dialog_after_every_sucessful_form_submission"; + } // namespace features } // namespace password_manager diff --git a/chromium/components/password_manager/core/common/password_manager_features.h b/chromium/components/password_manager/core/common/password_manager_features.h index e7c8ee3ad5a..7ad602aa0b0 100644 --- a/chromium/components/password_manager/core/common/password_manager_features.h +++ b/chromium/components/password_manager/core/common/password_manager_features.h @@ -18,6 +18,7 @@ namespace features { // alongside the definition of their values in the .cc file. extern const base::Feature kBiometricTouchToFill; +extern const base::Feature kCompromisedPasswordsReengagement; extern const base::Feature kEditPasswordsInDesktopSettings; extern const base::Feature kDeleteCorruptedPasswords; extern const base::Feature kEnableOverwritingPlaceholderUsernames; @@ -32,6 +33,7 @@ extern const base::Feature kPasswordImport; extern const base::Feature kPasswordManagerOnboardingAndroid; extern const base::Feature kRecoverFromNeverSaveAndroid; extern const base::Feature kUsernameFirstFlow; +extern const base::Feature kWellKnownChangePassword; // Field trial and corresponding parameters. // To manually override this, start Chrome with the following parameters: @@ -45,6 +47,12 @@ extern const char kGenerationRequirementsVersion[]; extern const char kGenerationRequirementsPrefixLength[]; extern const char kGenerationRequirementsTimeout[]; +// Password change feature variation. +// The new variation will allow showing credential leaked dialog after +// every form submission (helpful for testing). +extern const char + kPasswordChangeWithForcedDialogAfterEverySuccessfulSubmission[]; + } // namespace features } // namespace password_manager diff --git a/chromium/components/password_manager/core/common/password_manager_pref_names.cc b/chromium/components/password_manager/core/common/password_manager_pref_names.cc index 9cac5d4b33d..c64c046fdff 100644 --- a/chromium/components/password_manager/core/common/password_manager_pref_names.cc +++ b/chromium/components/password_manager/core/common/password_manager_pref_names.cc @@ -63,5 +63,10 @@ const char kPasswordLeakDetectionEnabled[] = const char kWasOnboardingFeatureCheckedBefore[] = "profile.was_pwm_onboarding_feature_checked_before"; +const char kProfileStoreDateLastUsedForFilling[] = + "password_manager.profile_store_date_last_used_for_filling"; +const char kAccountStoreDateLastUsedForFilling[] = + "password_manager.account_store_date_last_used_for_filling"; + } // namespace prefs } // namespace password_manager diff --git a/chromium/components/password_manager/core/common/password_manager_pref_names.h b/chromium/components/password_manager/core/common/password_manager_pref_names.h index 0f45890c207..4f350f60e8b 100644 --- a/chromium/components/password_manager/core/common/password_manager_pref_names.h +++ b/chromium/components/password_manager/core/common/password_manager_pref_names.h @@ -99,6 +99,11 @@ extern const char kPasswordLeakDetectionEnabled[]; // subsequent feature checks to ensure data completeness. extern const char kWasOnboardingFeatureCheckedBefore[]; +// Timestamps of when credentials from the profile / account store were last +// used to fill a form, in microseconds since Windows epoch. +extern const char kProfileStoreDateLastUsedForFilling[]; +extern const char kAccountStoreDateLastUsedForFilling[]; + } // namespace prefs } // namespace password_manager diff --git a/chromium/components/password_manager/core/common/password_manager_ui.h b/chromium/components/password_manager/core/common/password_manager_ui.h index 28c077ca476..ce1a22c2a1f 100644 --- a/chromium/components/password_manager/core/common/password_manager_ui.h +++ b/chromium/components/password_manager/core/common/password_manager_ui.h @@ -46,6 +46,15 @@ enum State { // The user used a profile credential to log in successfully and should see a // prompt that allows them to move the credential to their account store. CAN_MOVE_PASSWORD_TO_ACCOUNT_STATE, + + // Last compromised password was updated and the user is safe. + PASSWORD_UPDATED_SAFE_STATE, + + // A compromised password was updated and the user has more to fix. + PASSWORD_UPDATED_MORE_TO_FIX, + + // Remind the user to fix the compromised passwords. + PASSWORD_UPDATED_UNSAFE_STATE, }; } // namespace ui diff --git a/chromium/components/password_manager/ios/account_select_fill_data.cc b/chromium/components/password_manager/ios/account_select_fill_data.cc index 5b0ad486c51..158bdd81ca9 100644 --- a/chromium/components/password_manager/ios/account_select_fill_data.cc +++ b/chromium/components/password_manager/ios/account_select_fill_data.cc @@ -33,7 +33,7 @@ void AccountSelectFillData::Add( auto iter_ok = forms_.insert( std::make_pair(form_data.form_renderer_id.value(), FormInfo())); FormInfo& form_info = iter_ok.first->second; - form_info.origin = form_data.origin; + form_info.origin = form_data.url; form_info.form_id = form_data.form_renderer_id; form_info.username_element_id = form_data.username_field.unique_renderer_id; form_info.password_element_id = form_data.password_field.unique_renderer_id; diff --git a/chromium/components/password_manager/ios/account_select_fill_data_unittest.cc b/chromium/components/password_manager/ios/account_select_fill_data_unittest.cc index 1e0018f25fd..92d0c38d8af 100644 --- a/chromium/components/password_manager/ios/account_select_fill_data_unittest.cc +++ b/chromium/components/password_manager/ios/account_select_fill_data_unittest.cc @@ -197,7 +197,7 @@ TEST_F(AccountSelectFillDataTest, GetFillData) { base::ASCIIToUTF16(kUsernames[1])); ASSERT_TRUE(fill_data); - EXPECT_EQ(form_data.origin, fill_data->origin); + EXPECT_EQ(form_data.url, fill_data->origin); EXPECT_EQ(form_data.form_renderer_id.value(), fill_data->form_id.value()); EXPECT_EQ(kUsernameUniqueIDs[form_i], fill_data->username_element_id.value()); diff --git a/chromium/components/password_manager/ios/js_password_manager.h b/chromium/components/password_manager/ios/js_password_manager.h index ebd5a2575d4..834d8e17143 100644 --- a/chromium/components/password_manager/ios/js_password_manager.h +++ b/chromium/components/password_manager/ios/js_password_manager.h @@ -7,7 +7,7 @@ #include "base/ios/block_types.h" #include "components/autofill/core/common/renderer_id.h" -#import "ios/web/public/deprecated/crw_js_injection_receiver.h" +#include "ios/web/public/js_messaging/web_frame.h" namespace autofill { struct PasswordFormFillData; @@ -17,13 +17,12 @@ namespace password_manager { struct FillData; -// Serializes |fillData| into a JSON string that can be used by the JS side -// of PasswordController. -NSString* SerializeFillData(const password_manager::FillData& fillData); +// Serializes |fillData| so it can be used by the JS side of PasswordController. +std::unique_ptr<base::Value> SerializeFillData( + const password_manager::FillData& fillData); -// Serializes |formData| into a JSON string that can be used by the JS side -// of PasswordController. -NSString* SerializePasswordFormFillData( +// Serializes |formData| so it can be used by the JS side of PasswordController. +std::unique_ptr<base::Value> SerializePasswordFormFillData( const autofill::PasswordFormFillData& formData); } // namespace password_manager @@ -42,8 +41,8 @@ NSString* SerializePasswordFormFillData( // For example the JSON string for a form with a single password field is: // [{"action":null,"method":null,"usernameElement":"","usernameValue":""," // passwords":[{"element":"","value":"asd"}]}] -- (void)findPasswordFormsWithCompletionHandler: - (void (^)(NSString*))completionHandler; +- (void)findPasswordFormsInFrame:(web::WebFrame*)frame + completionHandler:(void (^)(NSString*))completionHandler; // Extracts the password form with the given name from a web page. // |completionHandler| is called with the JSON string containing the info about @@ -53,6 +52,7 @@ NSString* SerializePasswordFormFillData( // {"action":null,"method":null,"usernameElement":"","usernameValue":"", // "passwords":[{"element":"","value":"asd"}]} - (void)extractForm:(autofill::FormRendererId)formIdentifier + inFrame:(web::WebFrame*)frame completionHandler:(void (^)(NSString*))completionHandler; // Fills in the password form specified by |JSONString| with the given @@ -61,30 +61,27 @@ NSString* SerializePasswordFormFillData( // |extractSubmittedFormWithCompletionHandler|. Calls |completionHandler| with // YES if the filling of the password was successful, NO otherwise. // |completionHandler| cannot be nil. -- (void)fillPasswordForm:(NSString*)JSONString +- (void)fillPasswordForm:(std::unique_ptr<base::Value>)form + inFrame:(web::WebFrame*)frame withUsername:(NSString*)username password:(NSString*)password - completionHandler:(void (^)(BOOL))completionHandler; + completionHandler:(void (^)(NSString*))completionHandler; // Fills new password field for (optional) |newPasswordIdentifier| and for // (optional) confirm password field |confirmPasswordIdentifier| in the form // identified by |formData|. Invokes |completionHandler| with true if any fields // were filled, false otherwise. - (void)fillPasswordForm:(autofill::FormRendererId)formIdentifier + inFrame:(web::WebFrame*)frame newPasswordIdentifier:(autofill::FieldRendererId)newPasswordIdentifier confirmPasswordIdentifier: (autofill::FieldRendererId)confirmPasswordIdentifier generatedPassword:(NSString*)generatedPassword - completionHandler:(void (^)(BOOL))completionHandler; + completionHandler:(void (^)(NSString*))completionHandler; // Sets up the next available unique ID value in a document. -- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID; - -// Designated initializer. |receiver| should not be nil. -- (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver - NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; +- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID + inFrame:(web::WebFrame*)frame; @end diff --git a/chromium/components/password_manager/ios/js_password_manager.mm b/chromium/components/password_manager/ios/js_password_manager.mm index 7766708a674..91edfacafaa 100644 --- a/chromium/components/password_manager/ios/js_password_manager.mm +++ b/chromium/components/password_manager/ios/js_password_manager.mm @@ -5,8 +5,6 @@ #import "components/password_manager/ios/js_password_manager.h" #include "base/check.h" -#include "base/json/json_writer.h" -#include "base/json/string_escape.h" #include "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" #include "base/values.h" @@ -20,18 +18,20 @@ using autofill::FormRendererId; using autofill::FieldRendererId; +using base::SysNSStringToUTF8; namespace password_manager { -NSString* SerializeFillData(const GURL& origin, - FormRendererId form_renderer_id, - FieldRendererId username_element, - const base::string16& username_value, - FieldRendererId password_element, - const base::string16& password_value) { - base::DictionaryValue rootDict; - rootDict.SetString("origin", origin.spec()); - rootDict.SetInteger("unique_renderer_id", form_renderer_id.value()); +std::unique_ptr<base::Value> SerializeFillData( + const GURL& origin, + FormRendererId form_renderer_id, + FieldRendererId username_element, + const base::string16& username_value, + FieldRendererId password_element, + const base::string16& password_value) { + auto rootDict = std::make_unique<base::DictionaryValue>(); + rootDict->SetString("origin", origin.spec()); + rootDict->SetInteger("unique_renderer_id", form_renderer_id.value()); auto fieldList = std::make_unique<base::ListValue>(); @@ -47,23 +47,22 @@ NSString* SerializeFillData(const GURL& origin, passwordField->SetString("value", password_value); fieldList->Append(std::move(passwordField)); - rootDict.Set("fields", std::move(fieldList)); + rootDict->Set("fields", std::move(fieldList)); - std::string jsonString; - base::JSONWriter::Write(rootDict, &jsonString); - return base::SysUTF8ToNSString(jsonString); + return rootDict; } -NSString* SerializePasswordFormFillData( +std::unique_ptr<base::Value> SerializePasswordFormFillData( const autofill::PasswordFormFillData& formData) { - return SerializeFillData(formData.origin, formData.form_renderer_id, + return SerializeFillData(formData.url, formData.form_renderer_id, formData.username_field.unique_renderer_id, formData.username_field.value, formData.password_field.unique_renderer_id, formData.password_field.value); } -NSString* SerializeFillData(const password_manager::FillData& fillData) { +std::unique_ptr<base::Value> SerializeFillData( + const password_manager::FillData& fillData) { return SerializeFillData( fillData.origin, fillData.form_id, fillData.username_element_id, fillData.username_value, fillData.password_element_id, @@ -72,88 +71,71 @@ NSString* SerializeFillData(const password_manager::FillData& fillData) { } // namespace password_manager -namespace { -// Sanitizes |JSONString| and wraps it in quotes so it can be injected safely in -// JavaScript. -NSString* JSONEscape(NSString* JSONString) { - return base::SysUTF8ToNSString( - base::GetQuotedJSONString(base::SysNSStringToUTF8(JSONString))); -} -} // namespace - -@implementation JsPasswordManager { - // The injection receiver used to evaluate JavaScript. - __weak CRWJSInjectionReceiver* _receiver; -} +@implementation JsPasswordManager -- (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver { - DCHECK(receiver); - self = [super init]; - if (self) { - _receiver = receiver; - } - return self; -} - -- (void)findPasswordFormsWithCompletionHandler: - (void (^)(NSString*))completionHandler { +- (void)findPasswordFormsInFrame:(web::WebFrame*)frame + completionHandler:(void (^)(NSString*))completionHandler { DCHECK(completionHandler); - [_receiver executeJavaScript:@"__gCrWeb.passwords.findPasswordForms()" - completionHandler:^(id result, NSError*) { - completionHandler(base::mac::ObjCCastStrict<NSString>(result)); - }]; + std::vector<base::Value> parameters; + autofill::ExecuteJavaScriptFunction("passwords.findPasswordForms", parameters, + frame, base::BindOnce(completionHandler)); } - (void)extractForm:(FormRendererId)formIdentifier + inFrame:(web::WebFrame*)frame completionHandler:(void (^)(NSString*))completionHandler { DCHECK(completionHandler); - NSString* extra = [NSString - stringWithFormat:@"__gCrWeb.passwords.getPasswordFormDataAsString(%u)", - formIdentifier.value()]; - [_receiver executeJavaScript:extra - completionHandler:^(id result, NSError*) { - completionHandler(base::mac::ObjCCastStrict<NSString>(result)); - }]; + std::vector<base::Value> parameters; + parameters.push_back(base::Value(static_cast<int>(formIdentifier.value()))); + autofill::ExecuteJavaScriptFunction("passwords.getPasswordFormDataAsString", + parameters, frame, + base::BindOnce(completionHandler)); } -- (void)fillPasswordForm:(NSString*)JSONString +// TODO(crbug.com/1075444): Receive strings as std::string as they are being +// converted twice. +- (void)fillPasswordForm:(std::unique_ptr<base::Value>)form + inFrame:(web::WebFrame*)frame withUsername:(NSString*)username password:(NSString*)password - completionHandler:(void (^)(BOOL))completionHandler { + completionHandler:(void (^)(NSString*))completionHandler { DCHECK(completionHandler); - NSString* script = [NSString - stringWithFormat:@"__gCrWeb.passwords.fillPasswordForm(%@, %@, %@)", - JSONString, JSONEscape(username), JSONEscape(password)]; - [_receiver executeJavaScript:script - completionHandler:^(id result, NSError*) { - completionHandler([result isEqual:@YES]); - }]; + std::vector<base::Value> parameters; + parameters.push_back(std::move(*form)); + parameters.push_back(base::Value(SysNSStringToUTF8(username))); + parameters.push_back(base::Value(SysNSStringToUTF8(password))); + autofill::ExecuteJavaScriptFunction("passwords.fillPasswordForm", parameters, + frame, base::BindOnce(completionHandler)); } +// TODO(crbug.com/1075444): Receive strings as std::string as they are being +// converted twice. - (void)fillPasswordForm:(FormRendererId)formIdentifier + inFrame:(web::WebFrame*)frame newPasswordIdentifier:(FieldRendererId)newPasswordIdentifier confirmPasswordIdentifier:(FieldRendererId)confirmPasswordIdentifier generatedPassword:(NSString*)generatedPassword - completionHandler:(void (^)(BOOL))completionHandler { - NSString* script = [NSString - stringWithFormat:@"__gCrWeb.passwords." - @"fillPasswordFormWithGeneratedPassword(%u, %u, %u, %@)", - formIdentifier.value(), newPasswordIdentifier.value(), - confirmPasswordIdentifier.value(), - JSONEscape(generatedPassword)]; - [_receiver executeJavaScript:script - completionHandler:^(id result, NSError*) { - completionHandler([result isEqual:@YES]); - }]; + completionHandler:(void (^)(NSString*))completionHandler { + DCHECK(completionHandler); + std::vector<base::Value> parameters; + parameters.push_back(base::Value(static_cast<int>(formIdentifier.value()))); + parameters.push_back( + base::Value(static_cast<int>(newPasswordIdentifier.value()))); + parameters.push_back( + base::Value(static_cast<int>(confirmPasswordIdentifier.value()))); + parameters.push_back(base::Value(SysNSStringToUTF8(generatedPassword))); + autofill::ExecuteJavaScriptFunction( + "passwords.fillPasswordFormWithGeneratedPassword", parameters, frame, + base::BindOnce(completionHandler)); } -- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID { - NSString* script = [NSString - stringWithFormat:@"__gCrWeb.fill.setUpForUniqueIDs(%d)", nextAvailableID]; - [_receiver executeJavaScript:script - completionHandler:^(id result, NSError*) { - [result isEqual:@YES]; - }]; +- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID + inFrame:(web::WebFrame*)frame { + std::vector<base::Value> parameters; + parameters.push_back(base::Value(static_cast<int>(nextAvailableID))); + autofill::ExecuteJavaScriptFunction("fill.setUpForUniqueIDs", parameters, + frame, + base::OnceCallback<void(NSString*)>()); } @end diff --git a/chromium/components/password_manager/ios/password_form_helper.h b/chromium/components/password_manager/ios/password_form_helper.h index aaab8314a8d..5d5939086ea 100644 --- a/chromium/components/password_manager/ios/password_form_helper.h +++ b/chromium/components/password_manager/ios/password_form_helper.h @@ -99,7 +99,10 @@ class WebState; (void (^)(BOOL found, const autofill::FormData& form))completionHandler; -- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID; +// Sets up the next available numeric value for setting unique renderer ids +// in |frame|. +- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID + inFrame:(web::WebFrame*)frame; // Creates a instance with the given WebState, observer and delegate. - (instancetype)initWithWebState:(web::WebState*)webState diff --git a/chromium/components/password_manager/ios/password_form_helper.mm b/chromium/components/password_manager/ios/password_form_helper.mm index db706546abd..7a30b33d7d8 100644 --- a/chromium/components/password_manager/ios/password_form_helper.mm +++ b/chromium/components/password_manager/ios/password_form_helper.mm @@ -18,6 +18,8 @@ #include "components/password_manager/ios/account_select_fill_data.h" #include "components/password_manager/ios/js_password_manager.h" #import "ios/web/public/js_messaging/web_frame.h" +#import "ios/web/public/js_messaging/web_frame_util.h" +#import "ios/web/public/js_messaging/web_frames_manager.h" #import "ios/web/public/web_state.h" #if !defined(__has_feature) || !__has_feature(objc_arc) @@ -123,8 +125,7 @@ constexpr char kCommandPrefix[] = "passwordForm"; _webState->AddObserver(_webStateObserverBridge.get()); _formActivityObserverBridge = std::make_unique<autofill::FormActivityObserverBridge>(_webState, self); - _jsPasswordManager = [[JsPasswordManager alloc] - initWithReceiver:_webState->GetJSInjectionReceiver()]; + _jsPasswordManager = [[JsPasswordManager alloc] init]; __weak PasswordFormHelper* weakSelf = self; auto callback = base::BindRepeating( @@ -238,7 +239,7 @@ constexpr char kCommandPrefix[] = "passwordForm"; withUsername:(const base::string16&)username password:(const base::string16&)password completionHandler:(nullable void (^)(BOOL))completionHandler { - if (formData.origin.GetOrigin() != self.lastCommittedURL.GetOrigin()) { + if (formData.url.GetOrigin() != self.lastCommittedURL.GetOrigin()) { if (completionHandler) { completionHandler(NO); } @@ -248,11 +249,12 @@ constexpr char kCommandPrefix[] = "passwordForm"; // Send JSON over to the web view. [self.jsPasswordManager fillPasswordForm:SerializePasswordFormFillData(formData) + inFrame:GetMainFrame(_webState) withUsername:base::SysUTF16ToNSString(username) password:base::SysUTF16ToNSString(password) - completionHandler:^(BOOL result) { + completionHandler:^(NSString* result) { if (completionHandler) { - completionHandler(result); + completionHandler([result isEqual:@"true"]); } }]; } @@ -278,23 +280,24 @@ constexpr char kCommandPrefix[] = "passwordForm"; __weak PasswordFormHelper* weakSelf = self; [self.jsPasswordManager - findPasswordFormsWithCompletionHandler:^(NSString* jsonString) { - std::vector<FormData> forms; - [weakSelf getPasswordFormsFromJSON:jsonString - pageURL:pageURL - forms:&forms]; - // Find the maximum extracted value. - uint32_t maxID = 0; - for (const auto& form : forms) { - if (form.unique_renderer_id) - maxID = std::max(maxID, form.unique_renderer_id.value()); - for (const auto& field : form.fields) { - if (field.unique_renderer_id) - maxID = std::max(maxID, field.unique_renderer_id.value()); - } - } - completionHandler(forms, maxID); - }]; + findPasswordFormsInFrame:GetMainFrame(_webState) + completionHandler:^(NSString* jsonString) { + std::vector<FormData> forms; + [weakSelf getPasswordFormsFromJSON:jsonString + pageURL:pageURL + forms:&forms]; + // Find the maximum extracted value. + uint32_t maxID = 0; + for (const auto& form : forms) { + if (form.unique_renderer_id) + maxID = std::max(maxID, form.unique_renderer_id.value()); + for (const auto& field : form.fields) { + if (field.unique_renderer_id) + maxID = std::max(maxID, field.unique_renderer_id.value()); + } + } + completionHandler(forms, maxID); + }]; } - (void)fillPasswordForm:(const autofill::PasswordFormFillData&)formData @@ -320,12 +323,13 @@ constexpr char kCommandPrefix[] = "passwordForm"; completionHandler:(nullable void (^)(BOOL))completionHandler { // Send JSON over to the web view. [self.jsPasswordManager fillPasswordForm:formIdentifier + inFrame:GetMainFrame(_webState) newPasswordIdentifier:newPasswordIdentifier confirmPasswordIdentifier:confirmPasswordIdentifier generatedPassword:generatedPassword - completionHandler:^(BOOL result) { + completionHandler:^(NSString* result) { if (completionHandler) { - completionHandler(result); + completionHandler([result isEqual:@"true"]); } }]; } @@ -335,11 +339,12 @@ constexpr char kCommandPrefix[] = "passwordForm"; (nullable void (^)(BOOL))completionHandler { [self.jsPasswordManager fillPasswordForm:SerializeFillData(fillData) + inFrame:GetMainFrame(_webState) withUsername:base::SysUTF16ToNSString(fillData.username_value) password:base::SysUTF16ToNSString(fillData.password_value) - completionHandler:^(BOOL result) { + completionHandler:^(NSString* result) { if (completionHandler) { - completionHandler(result); + completionHandler([result isEqual:@"true"]); } }]; } @@ -410,11 +415,14 @@ constexpr char kCommandPrefix[] = "passwordForm"; }; [self.jsPasswordManager extractForm:formIdentifier + inFrame:GetMainFrame(_webState) completionHandler:extractFormDataCompletionHandler]; } -- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID { - [self.jsPasswordManager setUpForUniqueIDsWithInitialState:nextAvailableID]; +- (void)setUpForUniqueIDsWithInitialState:(uint32_t)nextAvailableID + inFrame:(web::WebFrame*)frame { + [self.jsPasswordManager setUpForUniqueIDsWithInitialState:nextAvailableID + inFrame:frame]; } @end diff --git a/chromium/components/password_manager/ios/password_form_helper_unittest.mm b/chromium/components/password_manager/ios/password_form_helper_unittest.mm index ccd4d33950f..7051050a218 100644 --- a/chromium/components/password_manager/ios/password_form_helper_unittest.mm +++ b/chromium/components/password_manager/ios/password_form_helper_unittest.mm @@ -51,12 +51,6 @@ using test_helpers::SetFillData; // Mocks JsPasswordManager to simluate javascript execution failure. @interface MockJsPasswordManager : JsPasswordManager -// Designated initializer. -- (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver - NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - // For the first |targetFailureCount| calls to // |fillPasswordForm:withUserName:password:completionHandler:|, skips the // invocation of the real JavaScript manager, giving the effect that password @@ -70,26 +64,24 @@ using test_helpers::SetFillData; NSUInteger _fillPasswordFormFailureCountRemaining; } -- (instancetype)initWithReceiver:(CRWJSInjectionReceiver*)receiver { - return [super initWithReceiver:receiver]; -} - - (void)setFillPasswordFormTargetFailureCount:(NSUInteger)targetFailureCount { _fillPasswordFormFailureCountRemaining = targetFailureCount; } -- (void)fillPasswordForm:(NSString*)JSONString +- (void)fillPasswordForm:(std::unique_ptr<base::Value>)form + inFrame:(web::WebFrame*)frame withUsername:(NSString*)username password:(NSString*)password - completionHandler:(void (^)(BOOL))completionHandler { + completionHandler:(void (^)(NSString*))completionHandler { if (_fillPasswordFormFailureCountRemaining > 0) { --_fillPasswordFormFailureCountRemaining; if (completionHandler) { - completionHandler(NO); + completionHandler(@"false"); } return; } - [super fillPasswordForm:JSONString + [super fillPasswordForm:std::move(form) + inFrame:frame withUsername:username password:password completionHandler:completionHandler]; @@ -331,8 +323,8 @@ TEST_F(PasswordFormHelperTest, FindAndFillOnePasswordForm) { // that completion handler is called with the proper values. TEST_F(PasswordFormHelperTest, FindAndFillMultiplePasswordForms) { // Fails the first call to fill password form. - MockJsPasswordManager* mockJsPasswordManager = [[MockJsPasswordManager alloc] - initWithReceiver:web_state()->GetJSInjectionReceiver()]; + MockJsPasswordManager* mockJsPasswordManager = + [[MockJsPasswordManager alloc] init]; [mockJsPasswordManager setFillPasswordFormTargetFailureCount:1]; [helper_ setJsPasswordManager:mockJsPasswordManager]; LoadHtml( @@ -370,8 +362,8 @@ TEST_F(PasswordFormHelperTest, FindAndFillMultiplePasswordForms) { // Tests that extractPasswordFormData extracts wanted form on page with mutiple // forms. TEST_F(PasswordFormHelperTest, ExtractPasswordFormData) { - MockJsPasswordManager* mockJsPasswordManager = [[MockJsPasswordManager alloc] - initWithReceiver:web_state()->GetJSInjectionReceiver()]; + MockJsPasswordManager* mockJsPasswordManager = + [[MockJsPasswordManager alloc] init]; [helper_ setJsPasswordManager:mockJsPasswordManager]; LoadHtml(@"<form><input id='u1' type='text' name='un1'>" "<input id='p1' type='password' name='pw1'></form>" diff --git a/chromium/components/password_manager/ios/resources/password_controller.js b/chromium/components/password_manager/ios/resources/password_controller.js index 55a75c9c671..6343a3c7381 100644 --- a/chromium/components/password_manager/ios/resources/password_controller.js +++ b/chromium/components/password_manager/ios/resources/password_controller.js @@ -112,7 +112,7 @@ const addSubmitButtonTouchEndHandler = function(form) { */ const onSubmitButtonTouchEnd = function(evt) { const form = evt.currentTarget.form; - const formData = __gCrWeb.passwords.getPasswordFormData(form); + const formData = __gCrWeb.passwords.getPasswordFormData(form, window); if (!formData) { return; } @@ -128,7 +128,7 @@ const onSubmitButtonTouchEnd = function(evt) { * @return {HTMLInputElement} */ const findInputByUniqueFieldId = function(inputs, identifier) { - const uniqueID = Symbol.for('__gChrome~uniqueID'); + const uniqueID = Symbol.for(__gCrWeb.fill.UNIQUE_ID_SYMBOL_NAME); for (let i = 0; i < inputs.length; ++i) { if (identifier === inputs[i][uniqueID]) { return inputs[i]; @@ -181,7 +181,7 @@ __gCrWeb.passwords['getPasswordFormDataAsString'] = function(identifier) { if (!el) { return '{}'; } - const formData = __gCrWeb.passwords.getPasswordFormData(el); + const formData = __gCrWeb.passwords.getPasswordFormData(el, window); if (!formData) { return '{}'; } @@ -198,7 +198,10 @@ __gCrWeb.passwords['getPasswordFormDataAsString'] = function(identifier) { * @param {AutofillFormData} formData Form data. * @param {string} username The username to fill. * @param {string} password The password to fill. - * @return {boolean} Whether a form field has been filled. + * @return {string} Whether a form field has been filled. + * + * TODO(crbug.com/1075444): Rewrite callback handler to accept various + * return types and return boolean. */ __gCrWeb.passwords['fillPasswordForm'] = function( formData, username, password) { @@ -206,9 +209,10 @@ __gCrWeb.passwords['fillPasswordForm'] = function( __gCrWeb.common.removeQueryAndReferenceFromURL(window.location.href); const origin = /** @type {string} */ (formData['origin']); if (!__gCrWeb.common.isSameOrigin(origin, normalizedOrigin)) { - return false; + return 'false'; } - return fillPasswordFormWithData(formData, username, password, window); + return fillPasswordFormWithData(formData, username, password, window) + .toString(); }; /** @@ -220,20 +224,23 @@ __gCrWeb.passwords['fillPasswordForm'] = function( * @param {number} confirmPasswordIdentifier The id of confirm password element * to fill. * @param {string} password The password to fill. - * @return {boolean} Whether new password field has been filled. + * @return {string} Whether new password field has been filled. + * + * TODO(crbug.com/1075444): Rewrite callback handler to accept various + * return types and return boolean. */ __gCrWeb.passwords['fillPasswordFormWithGeneratedPassword'] = function( formIdentifier, newPasswordIdentifier, confirmPasswordIdentifier, password) { const form = __gCrWeb.form.getFormElementFromUniqueFormId(formIdentifier); if (!form) { - return false; + return 'false'; } const inputs = getFormInputElements(form); const newPasswordField = findInputByUniqueFieldId(inputs, newPasswordIdentifier); if (!newPasswordField) { - return false; + return 'false'; } // Avoid resetting if same value, as it moves cursor to the end. if (newPasswordField.value !== password) { @@ -244,7 +251,7 @@ __gCrWeb.passwords['fillPasswordFormWithGeneratedPassword'] = function( if (confirmPasswordField && confirmPasswordField.value !== password) { __gCrWeb.fill.setInputElementValue(password, confirmPasswordField); } - return true; + return 'true'; }; /** @@ -341,7 +348,7 @@ const getPasswordFormDataList = function(formDataList, win) { const doc = win.document; const forms = doc.forms; for (let i = 0; i < forms.length; i++) { - const formData = __gCrWeb.passwords.getPasswordFormData(forms[i]); + const formData = __gCrWeb.passwords.getPasswordFormData(forms[i], win); if (formData) { formDataList.push(formData); addSubmitButtonTouchEndHandler(forms[i]); @@ -384,13 +391,14 @@ function getPasswordFormDataFromUnownedElements_(formDataList, window) { /** * Returns a JS object containing the data from |formElement|. * @param {HTMLFormElement} formElement An HTML Form element. + * @param {Window} win A window or a frame containing formData. * @return {Object} Object of data from formElement. */ -__gCrWeb.passwords.getPasswordFormData = function(formElement) { +__gCrWeb.passwords.getPasswordFormData = function(formElement, win) { const extractMask = __gCrWeb.fill.EXTRACT_MASK_VALUE; const formData = {}; const ok = __gCrWeb.fill.webFormElementToFormData( - window, formElement, null /* formControlElement */, extractMask, formData, + win, formElement, null /* formControlElement */, extractMask, formData, null /* field */); return ok ? formData : null; }; diff --git a/chromium/components/password_manager/ios/test_helpers.cc b/chromium/components/password_manager/ios/test_helpers.cc index 103d97451bd..b8e06bb2e0b 100644 --- a/chromium/components/password_manager/ios/test_helpers.cc +++ b/chromium/components/password_manager/ios/test_helpers.cc @@ -16,7 +16,7 @@ using password_manager::FillData; namespace test_helpers { -void SetPasswordFormFillData(const std::string& origin, +void SetPasswordFormFillData(const std::string& url, const char* form_name, uint32_t unique_renderer_id, const char* username_field, @@ -29,7 +29,7 @@ void SetPasswordFormFillData(const std::string& origin, const char* additional_password, bool wait_for_username, PasswordFormFillData* form_data) { - form_data->origin = GURL(origin); + form_data->url = GURL(url); form_data->name = base::UTF8ToUTF16(form_name); form_data->form_renderer_id = FormRendererId(unique_renderer_id); autofill::FormFieldData username; |