| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/password_manager/core/browser/password_generation_manager.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/metrics/field_trial.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "components/autofill/core/browser/autofill_field.h" |
| #include "components/autofill/core/browser/autofill_metrics.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/password_requirements_spec_fetcher.h" |
| #include "components/autofill/core/browser/proto/password_requirements.pb.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill/core/common/form_field_data.h" |
| #include "components/autofill/core/common/password_form_generation_data.h" |
| #include "components/autofill/core/common/signatures_util.h" |
| #include "components/password_manager/core/browser/password_autofill_manager.h" |
| #include "components/password_manager/core/browser/password_manager.h" |
| #include "components/password_manager/core/browser/password_requirements_service.h" |
| #include "components/password_manager/core/browser/stub_password_manager_client.h" |
| #include "components/password_manager/core/browser/stub_password_manager_driver.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/pref_service.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/variations/entropy_provider.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| using autofill::FormStructure; |
| using base::ASCIIToUTF16; |
| using testing::_; |
| |
| namespace password_manager { |
| |
| namespace { |
| |
| // Magic constants. |
| // Host (suffix) of a server for which no domain wide requirements are given. |
| constexpr char kNoServerResponse[] = "www.no_server_response.com"; |
| // Host (suffix) of a server for which the autofill requirements are overriden |
| // via a domain wide spec. |
| constexpr char kHasServerResponse[] = "www.has_server_response.com"; |
| |
| class TestPasswordManagerDriver : public StubPasswordManagerDriver { |
| public: |
| explicit TestPasswordManagerDriver(PasswordManagerClient* client) |
| : password_manager_(client), |
| password_generation_manager_(client, this), |
| password_autofill_manager_(this, nullptr, client) {} |
| ~TestPasswordManagerDriver() override {} |
| |
| // PasswordManagerDriver implementation. |
| PasswordGenerationManager* GetPasswordGenerationManager() override { |
| return &password_generation_manager_; |
| } |
| PasswordManager* GetPasswordManager() override { return &password_manager_; } |
| PasswordAutofillManager* GetPasswordAutofillManager() override { |
| return &password_autofill_manager_; |
| } |
| void FormsEligibleForGenerationFound( |
| const std::vector<autofill::PasswordFormGenerationData>& forms) override { |
| found_forms_eligible_for_generation_.insert( |
| found_forms_eligible_for_generation_.begin(), forms.begin(), |
| forms.end()); |
| } |
| |
| const std::vector<autofill::PasswordFormGenerationData>& |
| GetFoundEligibleForGenerationForms() { |
| return found_forms_eligible_for_generation_; |
| } |
| |
| MOCK_METHOD0(AllowToRunFormClassifier, void()); |
| |
| private: |
| PasswordManager password_manager_; |
| PasswordGenerationManager password_generation_manager_; |
| PasswordAutofillManager password_autofill_manager_; |
| std::vector<autofill::PasswordFormGenerationData> |
| found_forms_eligible_for_generation_; |
| }; |
| |
| autofill::PasswordRequirementsSpec GetDomainWideRequirements() { |
| autofill::PasswordRequirementsSpec spec; |
| spec.set_max_length(7); |
| spec.set_priority(20); |
| return spec; |
| } |
| |
| autofill::PasswordRequirementsSpec GetFieldRequirements() { |
| autofill::PasswordRequirementsSpec spec; |
| spec.set_max_length(8); |
| spec.set_priority(10); |
| return spec; |
| } |
| |
| class FakePasswordRequirementsSpecFetcher |
| : public autofill::PasswordRequirementsSpecFetcher { |
| public: |
| using FetchCallback = |
| autofill::PasswordRequirementsSpecFetcher::FetchCallback; |
| |
| FakePasswordRequirementsSpecFetcher() = default; |
| ~FakePasswordRequirementsSpecFetcher() override = default; |
| |
| void Fetch(GURL origin, FetchCallback callback) override { |
| if (origin.GetOrigin().host_piece().find(kNoServerResponse) != |
| std::string::npos) { |
| std::move(callback).Run(autofill::PasswordRequirementsSpec()); |
| } else if (origin.GetOrigin().host_piece().find(kHasServerResponse) != |
| std::string::npos) { |
| std::move(callback).Run(GetDomainWideRequirements()); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| }; |
| |
| class MockPasswordManagerClient : public StubPasswordManagerClient { |
| public: |
| MOCK_CONST_METHOD0(GetPasswordSyncState, SyncState()); |
| MOCK_CONST_METHOD0(IsSavingAndFillingEnabledForCurrentPage, bool()); |
| MOCK_CONST_METHOD0(IsIncognito, bool()); |
| |
| explicit MockPasswordManagerClient(std::unique_ptr<PrefService> prefs) |
| : prefs_(std::move(prefs)), |
| store_(new TestPasswordStore), |
| driver_(this), |
| password_requirements_service_( |
| std::make_unique<FakePasswordRequirementsSpecFetcher>()) {} |
| |
| ~MockPasswordManagerClient() override { store_->ShutdownOnUIThread(); } |
| |
| PasswordStore* GetPasswordStore() const override { return store_.get(); } |
| PrefService* GetPrefs() const override { return prefs_.get(); } |
| 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_; |
| } |
| |
| TestPasswordManagerDriver* test_driver() { return &driver_; } |
| |
| private: |
| std::unique_ptr<PrefService> prefs_; |
| scoped_refptr<TestPasswordStore> store_; |
| TestPasswordManagerDriver driver_; |
| PasswordRequirementsService password_requirements_service_; |
| GURL last_committed_url_; |
| }; |
| |
| } // anonymous namespace |
| |
| class PasswordGenerationManagerTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| // Construct a PrefService and register all necessary prefs before handing |
| // it off to |client_|, as the initialization flow of |client_| will |
| // indirectly cause those prefs to be immediately accessed. |
| std::unique_ptr<TestingPrefServiceSimple> prefs( |
| new TestingPrefServiceSimple()); |
| prefs->registry()->RegisterBooleanPref(prefs::kCredentialsEnableService, |
| true); |
| client_.reset(new MockPasswordManagerClient(std::move(prefs))); |
| } |
| |
| void TearDown() override { client_.reset(); } |
| |
| PasswordGenerationManager* GetGenerationManager() { |
| return client_->test_driver()->GetPasswordGenerationManager(); |
| } |
| |
| TestPasswordManagerDriver* GetTestDriver() { return client_->test_driver(); } |
| |
| bool IsGenerationEnabled() { |
| return GetGenerationManager()->IsGenerationEnabled(true); |
| } |
| |
| void DetectFormsEligibleForGeneration( |
| const std::vector<autofill::FormStructure*>& forms) { |
| GetGenerationManager()->DetectFormsEligibleForGeneration(forms); |
| } |
| |
| base::test::ScopedTaskEnvironment task_environment_; |
| std::unique_ptr<MockPasswordManagerClient> client_; |
| }; |
| |
| TEST_F(PasswordGenerationManagerTest, IsGenerationEnabled) { |
| // Enabling the PasswordManager and password sync should cause generation to |
| // be enabled, unless the sync is with a custom passphrase. |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(true)); |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(SYNCING_NORMAL_ENCRYPTION)); |
| EXPECT_TRUE(IsGenerationEnabled()); |
| |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(SYNCING_WITH_CUSTOM_PASSPHRASE)); |
| EXPECT_TRUE(IsGenerationEnabled()); |
| |
| // Disabling password syncing should cause generation to be disabled. |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(NOT_SYNCING)); |
| EXPECT_FALSE(IsGenerationEnabled()); |
| |
| // Disabling the PasswordManager should cause generation to be disabled even |
| // if syncing is enabled. |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(false)); |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(SYNCING_NORMAL_ENCRYPTION)); |
| EXPECT_FALSE(IsGenerationEnabled()); |
| |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(SYNCING_WITH_CUSTOM_PASSPHRASE)); |
| EXPECT_FALSE(IsGenerationEnabled()); |
| } |
| |
| // Verify that password requirements received from the autofill server are |
| // stored and that domain-wide password requirements are fetched as well. |
| TEST_F(PasswordGenerationManagerTest, ProcessPasswordRequirements) { |
| // Setup so that IsGenerationEnabled() returns true. |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(true)); |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(SYNCING_NORMAL_ENCRYPTION)); |
| struct { |
| const char* name; |
| bool has_domain_wide_requirements = false; |
| bool has_field_requirements = false; |
| autofill::PasswordRequirementsSpec expected_spec; |
| } kTests[] = { |
| { |
| .name = "No known requirements", |
| .expected_spec = autofill::PasswordRequirementsSpec(), |
| }, |
| { |
| .name = "Only domain wide requirements", |
| .has_domain_wide_requirements = true, |
| .expected_spec = GetDomainWideRequirements(), |
| }, |
| { |
| .name = "Only field requirements", |
| .has_field_requirements = true, |
| .expected_spec = GetFieldRequirements(), |
| }, |
| { |
| .name = "Domain wide requirements take precedence", |
| .has_domain_wide_requirements = true, |
| .has_field_requirements = true, |
| .expected_spec = GetDomainWideRequirements(), |
| }, |
| }; |
| |
| // The purpose of this counter is to generate unique URLs and field signatures |
| // so that no caching can happen between test runs. If this causes issues in |
| // the future, the test should be converted into a parameterized test that |
| // creates unqieue PasswordRequirementsService instances for each run. |
| int test_counter = 0; |
| for (const auto& test : kTests) { |
| SCOPED_TRACE(test.name); |
| ++test_counter; |
| |
| autofill::FormFieldData username; |
| username.label = ASCIIToUTF16("username"); |
| username.name = ASCIIToUTF16("login"); |
| username.form_control_type = "text"; |
| |
| autofill::FormFieldData password; |
| password.label = ASCIIToUTF16("password"); |
| password.name = |
| ASCIIToUTF16(base::StringPrintf("password%d", test_counter)); |
| password.form_control_type = "password"; |
| |
| autofill::FormData account_creation_form; |
| account_creation_form.origin = GURL("http://accounts.yahoo.com/"); |
| account_creation_form.action = GURL("http://accounts.yahoo.com/signup"); |
| account_creation_form.name = ASCIIToUTF16("account_creation_form"); |
| account_creation_form.fields.push_back(username); |
| account_creation_form.fields.push_back(password); |
| |
| autofill::FormStructure form(account_creation_form); |
| |
| std::vector<autofill::FormStructure*> forms = {&form}; |
| |
| // EMAIL_ADDRESS = 9 |
| // ACCOUNT_CREATION_PASSWORD = 76 |
| autofill::AutofillQueryResponseContents response; |
| response.add_field()->set_overall_type_prediction(9); |
| response.add_field()->set_overall_type_prediction(76); |
| |
| if (test.has_field_requirements) { |
| *response.mutable_field(1)->mutable_password_requirements() = |
| GetFieldRequirements(); |
| } |
| // Configure the last committed entry URL with some magic constants for |
| // which the FakePasswordRequirementsFetcher is configured to respond |
| // with a filled or empty response. |
| GURL origin(base::StringPrintf("https://%d-%s/", test_counter, |
| test.has_domain_wide_requirements |
| ? kHasServerResponse |
| : kNoServerResponse)); |
| client_->SetLastCommittedEntryUrl(origin); |
| |
| std::string response_string; |
| ASSERT_TRUE(response.SerializeToString(&response_string)); |
| autofill::FormStructure::ParseQueryResponse(response_string, forms, |
| nullptr); |
| |
| GetGenerationManager()->PrefetchSpec(origin.GetOrigin()); |
| |
| // Processs the password requirements with expected side effects of |
| // either storing the requirements from the AutofillQueryResponseContents) |
| // in the PasswordRequirementsService. |
| GetGenerationManager()->ProcessPasswordRequirements(forms); |
| |
| // Validate the result. |
| autofill::FormSignature form_signature = |
| autofill::CalculateFormSignature(account_creation_form); |
| autofill::FieldSignature field_signature = |
| autofill::CalculateFieldSignatureForField(password); |
| autofill::PasswordRequirementsSpec spec = |
| client_->GetPasswordRequirementsService()->GetSpec( |
| origin, form_signature, field_signature); |
| EXPECT_EQ(test.expected_spec.max_length(), spec.max_length()); |
| } |
| } |
| |
| TEST_F(PasswordGenerationManagerTest, DetectFormsEligibleForGeneration) { |
| // Setup so that IsGenerationEnabled() returns true. |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(true)); |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(SYNCING_NORMAL_ENCRYPTION)); |
| |
| autofill::FormData login_form; |
| login_form.origin = GURL("http://www.yahoo.com/login/"); |
| autofill::FormFieldData username; |
| username.label = ASCIIToUTF16("username"); |
| username.name = ASCIIToUTF16("login"); |
| username.form_control_type = "text"; |
| login_form.fields.push_back(username); |
| autofill::FormFieldData password; |
| password.label = ASCIIToUTF16("password"); |
| password.name = ASCIIToUTF16("password"); |
| password.form_control_type = "password"; |
| login_form.fields.push_back(password); |
| autofill::FormStructure form1(login_form); |
| std::vector<autofill::FormStructure*> forms; |
| forms.push_back(&form1); |
| |
| autofill::FormData account_creation_form; |
| account_creation_form.origin = GURL("http://accounts.yahoo.com/"); |
| account_creation_form.action = GURL("http://accounts.yahoo.com/signup"); |
| account_creation_form.name = ASCIIToUTF16("account_creation_form"); |
| account_creation_form.fields.push_back(username); |
| account_creation_form.fields.push_back(password); |
| autofill::FormFieldData confirm_password; |
| confirm_password.label = ASCIIToUTF16("confirm_password"); |
| confirm_password.name = ASCIIToUTF16("confirm_password"); |
| confirm_password.form_control_type = "password"; |
| account_creation_form.fields.push_back(confirm_password); |
| autofill::FormSignature account_creation_form_signature = |
| autofill::CalculateFormSignature(account_creation_form); |
| autofill::FieldSignature account_creation_field_signature = |
| autofill::CalculateFieldSignatureForField(password); |
| autofill::FieldSignature confirmation_field_signature = |
| autofill::CalculateFieldSignatureForField(confirm_password); |
| autofill::FormStructure form2(account_creation_form); |
| forms.push_back(&form2); |
| |
| autofill::FormData change_password_form; |
| change_password_form.origin = GURL("http://accounts.yahoo.com/"); |
| change_password_form.action = GURL("http://accounts.yahoo.com/change"); |
| change_password_form.name = ASCIIToUTF16("change_password_form"); |
| change_password_form.fields.push_back(password); |
| change_password_form.fields[0].name = ASCIIToUTF16("new_password"); |
| change_password_form.fields.push_back(confirm_password); |
| autofill::FormStructure form3(change_password_form); |
| autofill::FormSignature change_password_form_signature = |
| autofill::CalculateFormSignature(change_password_form); |
| autofill::FieldSignature change_password_field_signature = |
| autofill::CalculateFieldSignatureForField(change_password_form.fields[0]); |
| forms.push_back(&form3); |
| |
| // Simulate the server response to set the field types. |
| // The server response numbers mean: |
| // EMAIL_ADDRESS = 9 |
| // PASSWORD = 75 |
| // ACCOUNT_CREATION_PASSWORD = 76 |
| // NEW_PASSWORD = 88 |
| // CONFIRMATION_PASSWORD = 95 |
| autofill::AutofillQueryResponseContents response; |
| response.add_field()->set_overall_type_prediction(9); |
| response.add_field()->set_overall_type_prediction(75); |
| response.add_field()->set_overall_type_prediction(9); |
| response.add_field()->set_overall_type_prediction(76); |
| response.add_field()->set_overall_type_prediction(75); |
| response.add_field()->set_overall_type_prediction(88); |
| response.add_field()->set_overall_type_prediction(95); |
| |
| std::string response_string; |
| ASSERT_TRUE(response.SerializeToString(&response_string)); |
| autofill::FormStructure::ParseQueryResponse(response_string, forms, nullptr); |
| |
| DetectFormsEligibleForGeneration(forms); |
| EXPECT_EQ(2u, GetTestDriver()->GetFoundEligibleForGenerationForms().size()); |
| EXPECT_EQ( |
| account_creation_form_signature, |
| GetTestDriver()->GetFoundEligibleForGenerationForms()[0].form_signature); |
| EXPECT_EQ( |
| account_creation_field_signature, |
| GetTestDriver()->GetFoundEligibleForGenerationForms()[0].field_signature); |
| EXPECT_FALSE(GetTestDriver() |
| ->GetFoundEligibleForGenerationForms()[0] |
| .confirmation_field_signature.has_value()); |
| |
| EXPECT_EQ( |
| change_password_form_signature, |
| GetTestDriver()->GetFoundEligibleForGenerationForms()[1].form_signature); |
| EXPECT_EQ( |
| change_password_field_signature, |
| GetTestDriver()->GetFoundEligibleForGenerationForms()[1].field_signature); |
| ASSERT_TRUE(GetTestDriver() |
| ->GetFoundEligibleForGenerationForms()[1] |
| .confirmation_field_signature.has_value()); |
| EXPECT_EQ(confirmation_field_signature, |
| GetTestDriver() |
| ->GetFoundEligibleForGenerationForms()[1] |
| .confirmation_field_signature.value()); |
| } |
| |
| TEST_F(PasswordGenerationManagerTest, UpdatePasswordSyncStateIncognito) { |
| // Disable password manager by going incognito. Even though password |
| // syncing is enabled, generation should still be disabled. |
| EXPECT_CALL(*client_, IsSavingAndFillingEnabledForCurrentPage()) |
| .WillRepeatedly(testing::Return(false)); |
| EXPECT_CALL(*client_, IsIncognito()).WillRepeatedly(testing::Return(true)); |
| PrefService* prefs = client_->GetPrefs(); |
| prefs->SetBoolean(prefs::kCredentialsEnableService, true); |
| EXPECT_CALL(*client_, GetPasswordSyncState()) |
| .WillRepeatedly(testing::Return(SYNCING_NORMAL_ENCRYPTION)); |
| |
| EXPECT_FALSE(IsGenerationEnabled()); |
| } |
| |
| } // namespace password_manager |