| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/password_manager/core/browser/password_save_manager_impl.h" |
| |
| #include "base/macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "build/build_config.h" |
| #include "components/autofill/core/browser/autofill_download_manager.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/form_parsing/form_parser.h" |
| #include "components/password_manager/core/browser/multi_store_password_save_manager.h" |
| #include "components/password_manager/core/browser/password_form_metrics_recorder.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/vote_uploads_test_matchers.h" |
| #include "components/password_manager/core/browser/votes_uploader.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using autofill::AutofillUploadContents; |
| using autofill::FormData; |
| using autofill::FormFieldData; |
| using autofill::FormStructure; |
| using autofill::PasswordFormFillData; |
| using autofill::mojom::SubmissionIndicatorEvent; |
| using base::ASCIIToUTF16; |
| using base::TestMockTimeTaskRunner; |
| using testing::_; |
| using testing::AllOf; |
| using testing::Contains; |
| using testing::DoAll; |
| using testing::ElementsAre; |
| using testing::IsEmpty; |
| using testing::Mock; |
| using testing::NiceMock; |
| using testing::Pointee; |
| using testing::Return; |
| using testing::SaveArg; |
| using testing::UnorderedElementsAre; |
| |
| namespace password_manager { |
| |
| namespace { |
| |
| // Indices of username and password fields in the observed form. |
| const int kUsernameFieldIndex = 1; |
| const int kPasswordFieldIndex = 2; |
| |
| MATCHER_P(FormHasUniqueKey, key, "") { |
| return ArePasswordFormUniqueKeysEqual(arg, key); |
| } |
| |
| void CheckPendingCredentials(const PasswordForm& expected, |
| const PasswordForm& actual) { |
| EXPECT_EQ(expected.signon_realm, actual.signon_realm); |
| 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); |
| EXPECT_EQ(expected.username_element, actual.username_element); |
| EXPECT_EQ(expected.password_element, actual.password_element); |
| EXPECT_EQ(expected.blocked_by_user, actual.blocked_by_user); |
| EXPECT_TRUE( |
| autofill::FormDataEqualForTesting(expected.form_data, actual.form_data)); |
| } |
| |
| struct ExpectedGenerationUKM { |
| base::Optional<int64_t> generation_popup_shown; |
| int64_t has_generated_password; |
| base::Optional<int64_t> generated_password_modified; |
| }; |
| |
| // Check that UKM |metric_name| in |entry| is equal to |expected|. |expected| == |
| // null means that no metric recording is expected. |
| void CheckMetric(const int64_t* expected, |
| const ukm::mojom::UkmEntry* entry, |
| const char* metric_name) { |
| SCOPED_TRACE(testing::Message("Checking UKM metric ") << metric_name); |
| |
| const int64_t* actual = |
| ukm::TestUkmRecorder::GetEntryMetric(entry, metric_name); |
| |
| ASSERT_EQ(!!expected, !!actual); |
| if (expected) |
| EXPECT_EQ(*expected, *actual); |
| } |
| |
| // Check that |recorder| records metrics |expected_metrics|. |
| void CheckPasswordGenerationUKM(const ukm::TestAutoSetUkmRecorder& recorder, |
| const ExpectedGenerationUKM& expected_metrics) { |
| auto entries = |
| recorder.GetEntriesByName(ukm::builders::PasswordForm::kEntryName); |
| ASSERT_EQ(1u, entries.size()); |
| const int64_t* expected_popup_shown = nullptr; |
| if (expected_metrics.generation_popup_shown) |
| expected_popup_shown = &expected_metrics.generation_popup_shown.value(); |
| CheckMetric(expected_popup_shown, entries[0], |
| ukm::builders::PasswordForm::kGeneration_PopupShownName); |
| |
| CheckMetric(&expected_metrics.has_generated_password, entries[0], |
| ukm::builders::PasswordForm::kGeneration_GeneratedPasswordName); |
| |
| const int64_t* expected_password_modified = nullptr; |
| if (expected_metrics.generated_password_modified) |
| expected_password_modified = |
| &expected_metrics.generated_password_modified.value(); |
| CheckMetric( |
| expected_password_modified, entries[0], |
| ukm::builders::PasswordForm::kGeneration_GeneratedPasswordModifiedName); |
| } |
| |
| class MockFormSaver : public StubFormSaver { |
| public: |
| // FormSaver: |
| MOCK_METHOD(PasswordForm, Blocklist, (PasswordStore::FormDigest), (override)); |
| MOCK_METHOD(void, |
| Unblocklist, |
| (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>(); |
| } |
| }; |
| |
| class MockPasswordManagerClient : public StubPasswordManagerClient { |
| public: |
| 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 { |
| public: |
| MockAutofillDownloadManager() |
| : AutofillDownloadManager(nullptr, &fake_observer) {} |
| |
| 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<autofill::FormSignature>& form_signatures) override {} |
| }; |
| |
| StubObserver fake_observer; |
| DISALLOW_COPY_AND_ASSIGN(MockAutofillDownloadManager); |
| }; |
| |
| } // namespace |
| |
| class PasswordSaveManagerImplTest : public testing::Test, |
| public testing::WithParamInterface<bool> { |
| public: |
| PasswordSaveManagerImplTest() |
| : votes_uploader_(&client_, false /* is_possible_change_password_form */), |
| task_runner_(new TestMockTimeTaskRunner) { |
| GURL origin = GURL("https://accounts.google.com/a/ServiceLoginAuth"); |
| GURL action = GURL("https://accounts.google.com/a/ServiceLogin"); |
| GURL psl_origin = GURL("https://myaccounts.google.com/a/ServiceLoginAuth"); |
| GURL psl_action = GURL("https://myaccounts.google.com/a/ServiceLogin"); |
| |
| observed_form_.url = origin; |
| observed_form_.action = action; |
| observed_form_.name = ASCIIToUTF16("sign-in"); |
| observed_form_.unique_renderer_id = autofill::FormRendererId(1); |
| observed_form_.is_form_tag = true; |
| |
| observed_form_only_password_fields_ = observed_form_; |
| |
| FormFieldData field; |
| field.name = ASCIIToUTF16("firstname"); |
| field.id_attribute = field.name; |
| field.name_attribute = field.name; |
| field.form_control_type = "text"; |
| field.unique_renderer_id = autofill::FieldRendererId(1); |
| observed_form_.fields.push_back(field); |
| |
| field.name = ASCIIToUTF16("username"); |
| field.id_attribute = field.name; |
| field.name_attribute = field.name; |
| field.form_control_type = "text"; |
| field.unique_renderer_id = autofill::FieldRendererId(2); |
| observed_form_.fields.push_back(field); |
| |
| field.name = ASCIIToUTF16("password"); |
| field.id_attribute = field.name; |
| field.name_attribute = field.name; |
| field.form_control_type = "password"; |
| field.unique_renderer_id = autofill::FieldRendererId(3); |
| observed_form_.fields.push_back(field); |
| observed_form_only_password_fields_.fields.push_back(field); |
| |
| field.name = ASCIIToUTF16("password2"); |
| field.id_attribute = field.name; |
| field.name_attribute = field.name; |
| field.form_control_type = "password"; |
| field.unique_renderer_id = autofill::FieldRendererId(5); |
| observed_form_only_password_fields_.fields.push_back(field); |
| |
| // On iOS the unique_id member uniquely addresses this field in the DOM. |
| // This is an ephemeral value which is not guaranteed to be stable across |
| // page loads. It serves to allow a given field to be found during the |
| // current navigation. |
| // TODO(crbug.com/896689): Expand the logic/application of this to other |
| // platforms and/or merge this concept with |unique_renderer_id|. |
| #if defined(OS_IOS) |
| for (auto& f : observed_form_.fields) { |
| f.unique_id = f.id_attribute; |
| } |
| for (auto& f : observed_form_only_password_fields_.fields) { |
| f.unique_id = f.id_attribute; |
| } |
| #endif |
| |
| submitted_form_ = observed_form_; |
| submitted_form_.fields[kUsernameFieldIndex].value = ASCIIToUTF16("user1"); |
| submitted_form_.fields[kPasswordFieldIndex].value = ASCIIToUTF16("secret1"); |
| |
| saved_match_.url = origin; |
| saved_match_.action = action; |
| saved_match_.signon_realm = "https://accounts.google.com/"; |
| saved_match_.username_value = ASCIIToUTF16("test@gmail.com"); |
| saved_match_.username_element = ASCIIToUTF16("field1"); |
| saved_match_.password_value = ASCIIToUTF16("test1"); |
| saved_match_.password_element = ASCIIToUTF16("field2"); |
| saved_match_.is_public_suffix_match = false; |
| saved_match_.scheme = PasswordForm::Scheme::kHtml; |
| saved_match_.in_store = PasswordForm::Store::kProfileStore; |
| |
| psl_saved_match_ = saved_match_; |
| 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; |
| |
| parsed_observed_form_ = saved_match_; |
| parsed_observed_form_.form_data = observed_form_; |
| parsed_observed_form_.username_element = |
| observed_form_.fields[kUsernameFieldIndex].name; |
| parsed_observed_form_.password_element = |
| observed_form_.fields[kPasswordFieldIndex].name; |
| |
| parsed_submitted_form_ = parsed_observed_form_; |
| parsed_submitted_form_.form_data = submitted_form_; |
| parsed_submitted_form_.username_value = |
| submitted_form_.fields[kUsernameFieldIndex].value; |
| parsed_submitted_form_.password_value = |
| submitted_form_.fields[kPasswordFieldIndex].value; |
| |
| fetcher_ = std::make_unique<FakeFormFetcher>(); |
| fetcher_->Fetch(); |
| |
| metrics_recorder_ = base::MakeRefCounted<PasswordFormMetricsRecorder>( |
| client_.IsCommittedMainFrameSecure(), client_.GetUkmSourceId(), |
| /*pref_service=*/nullptr); |
| auto mock_form_saver = std::make_unique<NiceMock<MockFormSaver>>(); |
| mock_form_saver_ = mock_form_saver.get(); |
| |
| if (!GetParam()) { |
| password_save_manager_impl_ = |
| std::make_unique<PasswordSaveManagerImpl>(std::move(mock_form_saver)); |
| } else { |
| password_save_manager_impl_ = std::make_unique< |
| MultiStorePasswordSaveManager>( |
| /*account_form_saver=*/std::move(mock_form_saver), |
| /*account_form_saver=*/std::make_unique<NiceMock<MockFormSaver>>()); |
| } |
| |
| password_save_manager_impl_->Init(&client_, fetcher_.get(), |
| metrics_recorder_, &votes_uploader_); |
| |
| ON_CALL(client_, GetAutofillDownloadManager()) |
| .WillByDefault(Return(&mock_autofill_download_manager_)); |
| ON_CALL(mock_autofill_download_manager_, |
| StartUploadRequest(_, _, _, _, _, _)) |
| .WillByDefault(Return(true)); |
| ON_CALL(*client_.GetPasswordFeatureManager(), GetDefaultPasswordStore) |
| .WillByDefault(Return(PasswordForm::Store::kProfileStore)); |
| } |
| |
| PasswordForm Parse(const FormData& form_data) { |
| return *FormDataParser().Parse(form_data, FormDataParser::Mode::kSaving); |
| } |
| |
| void DestroySaveManagerAndMetricsRecorder() { |
| password_save_manager_impl_.reset(); |
| metrics_recorder_.reset(); |
| } |
| |
| MockPasswordManagerClient* client() { return &client_; } |
| |
| PasswordSaveManagerImpl* password_save_manager_impl() { |
| return password_save_manager_impl_.get(); |
| } |
| |
| MockFormSaver* mock_form_saver() { return mock_form_saver_; } |
| |
| FakeFormFetcher* fetcher() { return fetcher_.get(); } |
| |
| PasswordFormMetricsRecorder* metrics_recorder() { |
| return metrics_recorder_.get(); |
| } |
| |
| MockAutofillDownloadManager* mock_autofill_download_manager() { |
| return &mock_autofill_download_manager_; |
| } |
| |
| TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); } |
| |
| void SetNonFederatedAndNotifyFetchCompleted( |
| const std::vector<const PasswordForm*>& non_federated) { |
| fetcher()->SetNonFederated(non_federated); |
| fetcher()->NotifyFetchCompleted(); |
| } |
| |
| FormData observed_form_; |
| FormData submitted_form_; |
| FormData observed_form_only_password_fields_; |
| PasswordForm saved_match_; |
| PasswordForm psl_saved_match_; |
| PasswordForm parsed_observed_form_; |
| PasswordForm parsed_submitted_form_; |
| |
| private: |
| NiceMock<MockPasswordManagerClient> client_; |
| VotesUploader votes_uploader_; |
| scoped_refptr<PasswordFormMetricsRecorder> metrics_recorder_; |
| scoped_refptr<TestMockTimeTaskRunner> task_runner_; |
| |
| // Define |fetcher_| before |password_save_manager_impl_|, because the former |
| // needs to outlive the latter. |
| std::unique_ptr<FakeFormFetcher> fetcher_; |
| std::unique_ptr<PasswordSaveManagerImpl> password_save_manager_impl_; |
| NiceMock<MockFormSaver>* mock_form_saver_; |
| NiceMock<MockAutofillDownloadManager> mock_autofill_download_manager_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PasswordSaveManagerImplTest); |
| }; |
| |
| TEST_P(PasswordSaveManagerImplTest, Blocklist) { |
| PasswordStore::FormDigest form_digest(PasswordForm::Scheme::kDigest, |
| "www.example.com", GURL("www.abc.com")); |
| EXPECT_CALL(*mock_form_saver(), Blocklist(form_digest)); |
| password_save_manager_impl()->Blocklist(form_digest); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, Unblocklist) { |
| PasswordStore::FormDigest form_digest(PasswordForm::Scheme::kDigest, |
| "www.example.com", GURL("www.abc.com")); |
| EXPECT_CALL(*mock_form_saver(), Unblocklist(form_digest)); |
| password_save_manager_impl()->Unblocklist(form_digest); |
| } |
| |
| // Tests creating pending credentials when the password store is empty. |
| TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsEmptyStore) { |
| fetcher()->NotifyFetchCompleted(); |
| |
| const base::Time kNow = base::Time::Now(); |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form_), &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| const PasswordForm& pending_credentials = |
| password_save_manager_impl()->GetPendingCredentials(); |
| CheckPendingCredentials(parsed_submitted_form_, pending_credentials); |
| EXPECT_GE(pending_credentials.date_last_used, kNow); |
| } |
| |
| // Tests creating pending credentials when new credentials are submitted and the |
| // store has another credentials saved. |
| TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsNewCredentials) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form_), &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| parsed_submitted_form_, |
| password_save_manager_impl()->GetPendingCredentials()); |
| } |
| |
| // Tests that when submitted credentials are equal to already saved one then |
| // pending credentials equal to saved match. |
| TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsAlreadySaved) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| submitted_form_.fields[kUsernameFieldIndex].value = |
| saved_match_.username_value; |
| submitted_form_.fields[kPasswordFieldIndex].value = |
| saved_match_.password_value; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form_), &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| saved_match_, password_save_manager_impl()->GetPendingCredentials()); |
| } |
| |
| // Tests that when submitted credentials are equal to already saved PSL |
| // credentials. |
| TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsPSLMatchSaved) { |
| PasswordForm expected = saved_match_; |
| |
| 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; |
| |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| submitted_form_.fields[kUsernameFieldIndex].value = |
| saved_match_.username_value; |
| submitted_form_.fields[kPasswordFieldIndex].value = |
| saved_match_.password_value; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form_), &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| expected, password_save_manager_impl()->GetPendingCredentials()); |
| } |
| |
| // Tests creating pending credentials when new credentials are different only in |
| // password with already saved one. |
| TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsPasswordOverriden) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| PasswordForm expected = saved_match_; |
| expected.password_value += ASCIIToUTF16("1"); |
| |
| submitted_form_.fields[kUsernameFieldIndex].value = |
| saved_match_.username_value; |
| submitted_form_.fields[kPasswordFieldIndex].value = expected.password_value; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form_), &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| expected, password_save_manager_impl()->GetPendingCredentials()); |
| } |
| |
| // Tests that when submitted credentials are equal to already saved one then |
| // pending credentials equal to saved match. |
| TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsUpdate) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| FormData submitted_form = observed_form_only_password_fields_; |
| submitted_form.fields[0].value = ASCIIToUTF16("strongpassword"); |
| submitted_form.fields[1].value = ASCIIToUTF16("verystrongpassword"); |
| |
| PasswordForm expected = saved_match_; |
| expected.password_value = ASCIIToUTF16("verystrongpassword"); |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form), &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| expected, password_save_manager_impl()->GetPendingCredentials()); |
| } |
| |
| // Tests creating pending credentials when a change password form is submitted |
| // and there are multiple saved forms. |
| TEST_P(PasswordSaveManagerImplTest, |
| CreatePendingCredentialsUpdateMultipleSaved) { |
| PasswordForm another_saved_match = saved_match_; |
| another_saved_match.username_value += ASCIIToUTF16("1"); |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_, &another_saved_match}); |
| |
| FormData submitted_form = observed_form_only_password_fields_; |
| submitted_form.fields[0].value = ASCIIToUTF16("strongpassword"); |
| submitted_form.fields[1].value = ASCIIToUTF16("verystrongpassword"); |
| |
| PasswordForm expected = saved_match_; |
| expected.password_value = ASCIIToUTF16("verystrongpassword"); |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form), &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| expected, password_save_manager_impl()->GetPendingCredentials()); |
| } |
| |
| // Tests creating pending credentials when the password field has an empty name. |
| TEST_P(PasswordSaveManagerImplTest, CreatePendingCredentialsEmptyName) { |
| fetcher()->NotifyFetchCompleted(); |
| |
| FormData anonymous_signup = observed_form_; |
| // There is an anonymous password field and set it as the new password field. |
| anonymous_signup.fields[2].name.clear(); |
| anonymous_signup.fields[2].value = ASCIIToUTF16("a password"); |
| anonymous_signup.fields[2].autocomplete_attribute = "new-password"; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(anonymous_signup), &observed_form_, anonymous_signup, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| EXPECT_EQ( |
| ASCIIToUTF16("a password"), |
| password_save_manager_impl()->GetPendingCredentials().password_value); |
| } |
| |
| // Tests creating pending credentials when the password store is empty. |
| TEST_P(PasswordSaveManagerImplTest, ResetPendingCredentials) { |
| fetcher()->NotifyFetchCompleted(); |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form_), &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| password_save_manager_impl()->ResetPendingCredentials(); |
| |
| // Check that save manager is in None state. |
| EXPECT_FALSE(password_save_manager_impl()->IsNewLogin()); |
| EXPECT_FALSE(password_save_manager_impl()->IsPasswordUpdate()); |
| EXPECT_FALSE(password_save_manager_impl()->HasGeneratedPassword()); |
| } |
| |
| // Tests that when credentials with a new username (i.e. not saved yet) is |
| // successfully submitted, then they are saved correctly. |
| TEST_P(PasswordSaveManagerImplTest, SaveNewCredentials) { |
| TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner()); |
| base::HistogramTester histogram_tester; |
| ukm::TestAutoSetUkmRecorder test_ukm_recorder; |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| FormData submitted_form = observed_form_; |
| base::string16 new_username = saved_match_.username_value + ASCIIToUTF16("1"); |
| base::string16 new_password = saved_match_.password_value + ASCIIToUTF16("1"); |
| submitted_form.fields[kUsernameFieldIndex].value = new_username; |
| submitted_form.fields[kPasswordFieldIndex].value = new_password; |
| |
| PasswordForm parsed_submitted_form = Parse(submitted_form); |
| // Set SubmissionIndicatorEvent to test metrics recording. |
| parsed_submitted_form.submission_event = |
| SubmissionIndicatorEvent::HTML_FORM_SUBMISSION; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| EXPECT_TRUE(password_save_manager_impl()->IsNewLogin()); |
| |
| PasswordForm saved_form; |
| std::vector<const PasswordForm*> best_matches; |
| EXPECT_CALL(*mock_form_saver(), Save(_, _, _)) |
| .WillOnce(DoAll(SaveArg<0>(&saved_form), SaveArg<1>(&best_matches))); |
| |
| password_save_manager_impl()->Save(&observed_form_, parsed_submitted_form); |
| |
| std::string expected_signon_realm = submitted_form.url.GetOrigin().spec(); |
| 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); |
| |
| EXPECT_EQ(submitted_form.fields[kUsernameFieldIndex].name, |
| saved_form.username_element); |
| EXPECT_EQ(submitted_form.fields[kPasswordFieldIndex].name, |
| saved_form.password_element); |
| EXPECT_EQ(std::vector<const PasswordForm*>{&saved_match_}, best_matches); |
| |
| // Check histograms. |
| histogram_tester.ExpectUniqueSample( |
| "PasswordManager.AcceptedSaveUpdateSubmissionIndicatorEvent", |
| SubmissionIndicatorEvent::HTML_FORM_SUBMISSION, 1); |
| |
| // Check UKM metrics. |
| DestroySaveManagerAndMetricsRecorder(); |
| ExpectedGenerationUKM expected_metrics = { |
| {} /* shown manually */, |
| 0 /* password generated */, |
| {} /* generated password is not modified */}; |
| |
| CheckPasswordGenerationUKM(test_ukm_recorder, expected_metrics); |
| } |
| |
| // Check that if there is saved PSL matched credentials with the same |
| // username/password as in submitted form, then the saved form is the same |
| // already saved only with origin and signon_realm from the submitted form. |
| TEST_P(PasswordSaveManagerImplTest, SavePSLToAlreadySaved) { |
| SetNonFederatedAndNotifyFetchCompleted({&psl_saved_match_}); |
| |
| FormData submitted_form = observed_form_; |
| submitted_form.fields[kUsernameFieldIndex].value = |
| psl_saved_match_.username_value; |
| submitted_form.fields[kPasswordFieldIndex].value = |
| psl_saved_match_.password_value; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form), &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| EXPECT_TRUE(password_save_manager_impl()->IsNewLogin()); |
| EXPECT_TRUE(password_save_manager_impl() |
| ->GetPendingCredentials() |
| .is_public_suffix_match); |
| |
| PasswordForm saved_form; |
| std::vector<const PasswordForm*> best_matches; |
| EXPECT_CALL(*mock_form_saver(), Save(_, _, _)) |
| .WillOnce(DoAll(SaveArg<0>(&saved_form), SaveArg<1>(&best_matches))); |
| |
| password_save_manager_impl()->Save(&observed_form_, Parse(submitted_form)); |
| |
| 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); |
| EXPECT_EQ(psl_saved_match_.username_element, saved_form.username_element); |
| EXPECT_EQ(psl_saved_match_.password_element, saved_form.password_element); |
| |
| EXPECT_EQ(std::vector<const PasswordForm*>{&psl_saved_match_}, best_matches); |
| } |
| |
| // Tests that when credentials with already saved username but with a new |
| // password are submitted, then the saved password is updated. |
| TEST_P(PasswordSaveManagerImplTest, OverridePassword) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| FormData submitted_form = observed_form_; |
| base::string16 username = saved_match_.username_value; |
| base::string16 new_password = saved_match_.password_value + ASCIIToUTF16("1"); |
| submitted_form.fields[kUsernameFieldIndex].value = username; |
| submitted_form.fields[kPasswordFieldIndex].value = new_password; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form), &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| EXPECT_FALSE(password_save_manager_impl()->IsNewLogin()); |
| EXPECT_TRUE(password_save_manager_impl()->IsPasswordUpdate()); |
| |
| PasswordForm updated_form; |
| EXPECT_CALL(*mock_form_saver(), Update(_, ElementsAre(Pointee(saved_match_)), |
| saved_match_.password_value)) |
| .WillOnce(SaveArg<0>(&updated_form)); |
| |
| password_save_manager_impl()->Save(&observed_form_, Parse(submitted_form)); |
| |
| EXPECT_TRUE(ArePasswordFormUniqueKeysEqual(saved_match_, updated_form)); |
| EXPECT_EQ(new_password, updated_form.password_value); |
| } |
| |
| // Tests that when the user changes password on a change password form then the |
| // saved password is updated. |
| TEST_P(PasswordSaveManagerImplTest, UpdatePasswordOnChangePasswordForm) { |
| PasswordForm not_best_saved_match = saved_match_; |
| PasswordForm saved_match_another_username = saved_match_; |
| saved_match_another_username.username_value += ASCIIToUTF16("1"); |
| |
| SetNonFederatedAndNotifyFetchCompleted( |
| {&saved_match_, ¬_best_saved_match, &saved_match_another_username}); |
| |
| FormData submitted_form = observed_form_only_password_fields_; |
| submitted_form.fields[0].value = saved_match_.password_value; |
| base::string16 new_password = saved_match_.password_value + ASCIIToUTF16("1"); |
| submitted_form.fields[1].value = new_password; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| Parse(submitted_form), &observed_form_only_password_fields_, |
| submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| EXPECT_FALSE(password_save_manager_impl()->IsNewLogin()); |
| EXPECT_TRUE(password_save_manager_impl()->IsPasswordUpdate()); |
| |
| PasswordForm updated_form; |
| EXPECT_CALL(*mock_form_saver(), |
| Update(_, |
| UnorderedElementsAre( |
| Pointee(saved_match_), Pointee(not_best_saved_match), |
| Pointee(saved_match_another_username)), |
| saved_match_.password_value)) |
| .WillOnce(SaveArg<0>(&updated_form)); |
| |
| password_save_manager_impl()->Save(&observed_form_only_password_fields_, |
| Parse(submitted_form)); |
| |
| EXPECT_TRUE(ArePasswordFormUniqueKeysEqual(saved_match_, updated_form)); |
| EXPECT_EQ(new_password, updated_form.password_value); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UpdateUsernameToAnotherFieldValue) { |
| TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner()); |
| fetcher()->NotifyFetchCompleted(); |
| |
| base::string16 user_chosen_username = ASCIIToUTF16("user_chosen_username"); |
| base::string16 automatically_chosen_username = |
| ASCIIToUTF16("automatically_chosen_username"); |
| submitted_form_.fields[0].value = user_chosen_username; |
| submitted_form_.fields[1].value = automatically_chosen_username; |
| PasswordForm parsed_submitted_form = Parse(submitted_form_); |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_only_password_fields_, |
| submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| EXPECT_EQ( |
| automatically_chosen_username, |
| password_save_manager_impl()->GetPendingCredentials().username_value); |
| |
| // Simulate username update from the prompt. |
| parsed_submitted_form.username_value = user_chosen_username; |
| parsed_submitted_form.username_element.clear(); |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_only_password_fields_, |
| submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| EXPECT_EQ( |
| user_chosen_username, |
| password_save_manager_impl()->GetPendingCredentials().username_value); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UpdateUsernameToAlreadyExisting) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| PasswordForm parsed_submitted_form = Parse(submitted_form_); |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| base::string16 new_username = saved_match_.username_value; |
| base::string16 expected_password = parsed_submitted_form_.password_value; |
| PasswordForm expected = saved_match_; |
| expected.password_value = expected_password; |
| |
| // Simulate username update from the prompt. |
| parsed_submitted_form.username_value = new_username; |
| parsed_submitted_form.username_element.clear(); |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| expected, password_save_manager_impl()->GetPendingCredentials()); |
| EXPECT_FALSE(password_save_manager_impl()->IsNewLogin()); |
| EXPECT_TRUE(password_save_manager_impl()->IsPasswordUpdate()); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UpdatePasswordValueEmptyStore) { |
| fetcher()->NotifyFetchCompleted(); |
| |
| PasswordForm parsed_submitted_form = Parse(submitted_form_); |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| base::string16 new_password = |
| parsed_submitted_form_.password_value + ASCIIToUTF16("1"); |
| PasswordForm expected = parsed_submitted_form; |
| expected.password_value = new_password; |
| expected.password_element.clear(); |
| |
| // Simulate password update from the prompt. |
| parsed_submitted_form.password_value = new_password; |
| parsed_submitted_form.password_element.clear(); |
| parsed_submitted_form.new_password_value.clear(); |
| parsed_submitted_form.new_password_element.clear(); |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| expected, password_save_manager_impl()->GetPendingCredentials()); |
| EXPECT_TRUE(password_save_manager_impl()->IsNewLogin()); |
| |
| // TODO(https://crbug.com/928690): implement not sending incorrect votes and |
| // check that StartUploadRequest is not called. |
| EXPECT_CALL(*mock_autofill_download_manager(), |
| StartUploadRequest(_, _, _, _, _, _)) |
| .Times(1); |
| password_save_manager_impl()->Save(&observed_form_, parsed_submitted_form); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UpdatePasswordValueToAlreadyExisting) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| // Emulate submitting form with known username and different password. |
| submitted_form_.fields[kUsernameFieldIndex].value = |
| saved_match_.username_value; |
| |
| PasswordForm parsed_submitted_form = Parse(submitted_form_); |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| // Simulate password update from the prompt to already saved one. |
| parsed_submitted_form.password_value = saved_match_.password_value; |
| parsed_submitted_form.password_element.clear(); |
| parsed_submitted_form.new_password_value.clear(); |
| parsed_submitted_form.new_password_element.clear(); |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form_, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| CheckPendingCredentials( |
| saved_match_, password_save_manager_impl()->GetPendingCredentials()); |
| |
| EXPECT_FALSE(password_save_manager_impl()->IsNewLogin()); |
| EXPECT_FALSE(password_save_manager_impl()->IsPasswordUpdate()); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UpdatePasswordValueMultiplePasswordFields) { |
| FormData submitted_form = observed_form_only_password_fields_; |
| |
| fetcher()->NotifyFetchCompleted(); |
| base::string16 password = ASCIIToUTF16("password1"); |
| base::string16 pin = ASCIIToUTF16("pin"); |
| submitted_form.fields[0].value = password; |
| submitted_form.fields[1].value = pin; |
| PasswordForm parsed_submitted_form = Parse(submitted_form); |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| // Check that a second password field is chosen for saving. |
| EXPECT_EQ( |
| pin, |
| password_save_manager_impl()->GetPendingCredentials().password_value); |
| |
| PasswordForm expected = password_save_manager_impl()->GetPendingCredentials(); |
| expected.password_value = password; |
| expected.password_element = submitted_form.fields[0].name; |
| |
| // Simulate that the user updates value to save for the first password field |
| // using the update prompt. |
| parsed_submitted_form.password_value = password; |
| parsed_submitted_form.password_element = submitted_form.fields[0].name; |
| parsed_submitted_form.new_password_value.clear(); |
| parsed_submitted_form.new_password_element.clear(); |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| // Check that newly created pending credentials are correct. |
| CheckPendingCredentials( |
| expected, password_save_manager_impl()->GetPendingCredentials()); |
| EXPECT_TRUE(password_save_manager_impl()->IsNewLogin()); |
| |
| // Check that a vote is sent for the field with the value which is chosen by |
| // the user. |
| std::map<base::string16, autofill::ServerFieldType> expected_types; |
| expected_types[expected.password_element] = autofill::PASSWORD; |
| |
| EXPECT_CALL(*mock_autofill_download_manager(), |
| StartUploadRequest(UploadedAutofillTypesAre(expected_types), |
| false, _, _, true, nullptr)); |
| |
| // Check that the password which was chosen by the user is saved. |
| PasswordForm saved_form; |
| EXPECT_CALL(*mock_form_saver(), Save(_, _, _)) |
| .WillOnce(SaveArg<0>(&saved_form)); |
| |
| password_save_manager_impl()->Save(&observed_form_, parsed_submitted_form); |
| CheckPendingCredentials(expected, saved_form); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, PresaveGeneratedPasswordEmptyStore) { |
| fetcher()->NotifyFetchCompleted(); |
| |
| EXPECT_FALSE(password_save_manager_impl()->HasGeneratedPassword()); |
| |
| // Check that the generated password is presaved. |
| PasswordForm saved_form; |
| EXPECT_CALL(*mock_form_saver(), Save(_, IsEmpty(), base::string16())) |
| .WillOnce(SaveArg<0>(&saved_form)); |
| |
| PasswordForm form_with_generated_password = parsed_submitted_form_; |
| |
| password_save_manager_impl()->PresaveGeneratedPassword( |
| form_with_generated_password); |
| |
| EXPECT_TRUE(password_save_manager_impl()->HasGeneratedPassword()); |
| EXPECT_EQ(saved_form.username_value, |
| form_with_generated_password.username_value); |
| EXPECT_EQ(saved_form.password_value, |
| form_with_generated_password.password_value); |
| |
| Mock::VerifyAndClearExpectations(mock_form_saver()); |
| |
| // Check that when the generated password is edited, then it's presaved. |
| form_with_generated_password.password_value += ASCIIToUTF16("1"); |
| EXPECT_CALL(*mock_form_saver(), |
| UpdateReplace(_, IsEmpty(), ASCIIToUTF16(""), |
| FormHasUniqueKey(form_with_generated_password))) |
| .WillOnce(SaveArg<0>(&saved_form)); |
| |
| password_save_manager_impl()->PresaveGeneratedPassword( |
| form_with_generated_password); |
| |
| EXPECT_TRUE(password_save_manager_impl()->HasGeneratedPassword()); |
| EXPECT_EQ(saved_form.username_value, |
| form_with_generated_password.username_value); |
| EXPECT_EQ(saved_form.password_value, |
| form_with_generated_password.password_value); |
| |
| Mock::VerifyAndClearExpectations(mock_form_saver()); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, PresaveGenerated_ModifiedUsername) { |
| fetcher()->NotifyFetchCompleted(); |
| |
| // Check that the generated password is presaved. |
| PasswordForm saved_form; |
| EXPECT_CALL(*mock_form_saver(), Save(_, _, _)) |
| .WillOnce(SaveArg<0>(&saved_form)); |
| PasswordForm form_with_generated_password = parsed_submitted_form_; |
| |
| password_save_manager_impl()->PresaveGeneratedPassword( |
| form_with_generated_password); |
| |
| Mock::VerifyAndClearExpectations(mock_form_saver()); |
| |
| // Check that when the username is edited, then it's presaved. |
| form_with_generated_password.username_value += ASCIIToUTF16("1"); |
| |
| EXPECT_CALL(*mock_form_saver(), UpdateReplace(_, IsEmpty(), ASCIIToUTF16(""), |
| FormHasUniqueKey(saved_form))) |
| .WillOnce(SaveArg<0>(&saved_form)); |
| |
| password_save_manager_impl()->PresaveGeneratedPassword( |
| form_with_generated_password); |
| |
| EXPECT_TRUE(password_save_manager_impl()->HasGeneratedPassword()); |
| EXPECT_EQ(saved_form.username_value, |
| form_with_generated_password.username_value); |
| EXPECT_EQ(saved_form.password_value, |
| form_with_generated_password.password_value); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, |
| PresaveGeneratedPasswordExistingCredential) { |
| SetNonFederatedAndNotifyFetchCompleted({&saved_match_}); |
| |
| // Check that the generated password is presaved. |
| PasswordForm saved_form; |
| EXPECT_CALL(*mock_form_saver(), Save(_, _, _)) |
| .WillOnce(SaveArg<0>(&saved_form)); |
| |
| PasswordForm form_with_generated_password = parsed_submitted_form_; |
| |
| // Check that the generated password is saved with the empty username when |
| // there is already a saved credential with the same username. |
| form_with_generated_password.username_value = saved_match_.username_value; |
| |
| password_save_manager_impl()->PresaveGeneratedPassword( |
| form_with_generated_password); |
| |
| EXPECT_TRUE(password_save_manager_impl()->HasGeneratedPassword()); |
| EXPECT_TRUE(saved_form.username_value.empty()); |
| EXPECT_EQ(form_with_generated_password.password_value, |
| saved_form.password_value); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, PasswordNoLongerGenerated) { |
| fetcher()->NotifyFetchCompleted(); |
| EXPECT_CALL(*mock_form_saver(), Save(_, _, _)); |
| PasswordForm submitted_form(parsed_observed_form_); |
| submitted_form.password_value = ASCIIToUTF16("password"); |
| password_save_manager_impl()->PresaveGeneratedPassword(submitted_form); |
| EXPECT_CALL(*mock_form_saver(), Remove(_)); |
| password_save_manager_impl()->PasswordNoLongerGenerated(); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UserEventsForGeneration_Accept) { |
| using GeneratedPasswordStatus = |
| PasswordFormMetricsRecorder::GeneratedPasswordStatus; |
| |
| base::HistogramTester histogram_tester; |
| |
| password_save_manager_impl()->PresaveGeneratedPassword(parsed_observed_form_); |
| |
| DestroySaveManagerAndMetricsRecorder(); |
| histogram_tester.ExpectUniqueSample( |
| "PasswordGeneration.UserDecision", |
| GeneratedPasswordStatus::kPasswordAccepted, 1); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UserEventsForGeneration_Edit) { |
| using GeneratedPasswordStatus = |
| PasswordFormMetricsRecorder::GeneratedPasswordStatus; |
| |
| PasswordForm submitted_form(parsed_observed_form_); |
| |
| base::HistogramTester histogram_tester; |
| |
| password_save_manager_impl()->PresaveGeneratedPassword(submitted_form); |
| |
| submitted_form.password_value += ASCIIToUTF16("1"); |
| |
| password_save_manager_impl()->PresaveGeneratedPassword(submitted_form); |
| |
| DestroySaveManagerAndMetricsRecorder(); |
| histogram_tester.ExpectUniqueSample("PasswordGeneration.UserDecision", |
| GeneratedPasswordStatus::kPasswordEdited, |
| 1); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, UserEventsForGeneration_Clear) { |
| using GeneratedPasswordStatus = |
| PasswordFormMetricsRecorder::GeneratedPasswordStatus; |
| |
| PasswordForm submitted_form(parsed_observed_form_); |
| |
| base::HistogramTester histogram_tester; |
| |
| password_save_manager_impl()->PresaveGeneratedPassword(submitted_form); |
| |
| submitted_form.password_value += ASCIIToUTF16("2"); |
| |
| password_save_manager_impl()->PresaveGeneratedPassword(submitted_form); |
| |
| password_save_manager_impl()->PasswordNoLongerGenerated(); |
| |
| DestroySaveManagerAndMetricsRecorder(); |
| histogram_tester.ExpectUniqueSample("PasswordGeneration.UserDecision", |
| GeneratedPasswordStatus::kPasswordDeleted, |
| 1); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, Update) { |
| base::HistogramTester histogram_tester; |
| |
| PasswordForm not_best_saved_match = saved_match_; |
| PasswordForm saved_match_another_username = saved_match_; |
| saved_match_another_username.username_value += ASCIIToUTF16("1"); |
| SetNonFederatedAndNotifyFetchCompleted( |
| {&saved_match_, &saved_match_another_username}); |
| |
| FormData submitted_form = observed_form_; |
| base::string16 username = saved_match_.username_value; |
| base::string16 new_password = saved_match_.password_value + ASCIIToUTF16("1"); |
| submitted_form.fields[kUsernameFieldIndex].value = username; |
| submitted_form.fields[kPasswordFieldIndex].value = new_password; |
| |
| PasswordForm parsed_submitted_form = Parse(submitted_form); |
| // Set SubmissionIndicatorEvent to test metrics recording. |
| parsed_submitted_form.submission_event = |
| SubmissionIndicatorEvent::HTML_FORM_SUBMISSION; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| parsed_submitted_form, &observed_form_, submitted_form, |
| /*is_http_auth=*/false, |
| /*is_credential_api_save=*/false); |
| |
| PasswordForm updated_form; |
| EXPECT_CALL( |
| *mock_form_saver(), |
| Update(_, |
| UnorderedElementsAre(Pointee(saved_match_), |
| Pointee(saved_match_another_username)), |
| saved_match_.password_value)) |
| .WillOnce(SaveArg<0>(&updated_form)); |
| |
| const base::Time kNow = base::Time::Now(); |
| |
| password_save_manager_impl()->Update(saved_match_, &observed_form_, |
| parsed_submitted_form); |
| |
| EXPECT_TRUE(ArePasswordFormUniqueKeysEqual(saved_match_, updated_form)); |
| EXPECT_EQ(new_password, updated_form.password_value); |
| EXPECT_GE(updated_form.date_last_used, kNow); |
| |
| // Check histograms. |
| histogram_tester.ExpectUniqueSample( |
| "PasswordManager.AcceptedSaveUpdateSubmissionIndicatorEvent", |
| SubmissionIndicatorEvent::HTML_FORM_SUBMISSION, 1); |
| } |
| |
| TEST_P(PasswordSaveManagerImplTest, HTTPAuthPasswordOverridden) { |
| PasswordForm http_auth_form = parsed_observed_form_; |
| http_auth_form.scheme = PasswordForm::Scheme::kBasic; |
| fetcher()->set_scheme(PasswordForm::Scheme::kBasic); |
| |
| PasswordForm saved_http_auth_form = http_auth_form; |
| const base::string16 username = ASCIIToUTF16("user1"); |
| const base::string16 password = ASCIIToUTF16("pass1"); |
| saved_http_auth_form.username_value = username; |
| saved_http_auth_form.password_value = password; |
| |
| SetNonFederatedAndNotifyFetchCompleted({&saved_http_auth_form}); |
| |
| // Check that if new password is submitted, then |form_manager_| is in state |
| // password overridden. |
| PasswordForm submitted_http_auth_form = saved_http_auth_form; |
| base::string16 new_password = password + ASCIIToUTF16("1"); |
| submitted_http_auth_form.password_value = new_password; |
| |
| password_save_manager_impl()->CreatePendingCredentials( |
| submitted_http_auth_form, &observed_form_, submitted_form_, |
| /*is_http_auth=*/true, |
| /*is_credential_api_save=*/false); |
| |
| EXPECT_FALSE(password_save_manager_impl()->IsNewLogin()); |
| EXPECT_TRUE(password_save_manager_impl()->IsPasswordUpdate()); |
| |
| // Check that the password is updated in the stored credential. |
| PasswordForm updated_form; |
| EXPECT_CALL(*mock_form_saver(), |
| Update(_, ElementsAre(Pointee(saved_http_auth_form)), password)) |
| .WillOnce(SaveArg<0>(&updated_form)); |
| |
| password_save_manager_impl()->Save(&observed_form_, submitted_http_auth_form); |
| |
| EXPECT_TRUE( |
| ArePasswordFormUniqueKeysEqual(saved_http_auth_form, updated_form)); |
| EXPECT_EQ(new_password, updated_form.password_value); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| PasswordSaveManagerImplTest, |
| testing::Values(false, true)); |
| |
| } // namespace password_manager |