| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| #include "chrome/browser/password_manager/android/password_generation_controller_impl.h" |
| |
| #include <map> |
| #include <utility> |
| |
| #include "base/functional/callback.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "chrome/browser/autofill/mock_manual_filling_controller.h" |
| #include "chrome/browser/password_manager/android/password_generation_dialog_view_interface.h" |
| #include "chrome/browser/password_manager/chrome_password_manager_client.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "components/autofill/core/browser/autofill_client.h" |
| #include "components/autofill/core/browser/test_autofill_client.h" |
| #include "components/autofill/core/browser/ui/accessory_sheet_enums.h" |
| #include "components/autofill/core/common/password_generation_util.h" |
| #include "components/password_manager/content/browser/content_password_manager_driver.h" |
| #include "components/password_manager/core/browser/mock_password_store_interface.h" |
| #include "components/password_manager/core/browser/password_autofill_manager.h" |
| #include "components/password_manager/core/browser/password_generation_frame_helper.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/stub_password_manager_client.h" |
| #include "components/password_manager/core/browser/stub_password_manager_driver.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "content/public/browser/web_contents.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| using autofill::password_generation::PasswordGenerationType; |
| using password_manager::metrics_util::GenerationDialogChoice; |
| |
| namespace { |
| using autofill::FooterCommand; |
| using autofill::mojom::FocusedFieldType; |
| using autofill::password_generation::PasswordGenerationUIData; |
| using base::ASCIIToUTF16; |
| using password_manager::ContentPasswordManagerDriver; |
| using password_manager::MockPasswordStoreInterface; |
| using password_manager::PasswordForm; |
| using testing::_; |
| using testing::AtMost; |
| using testing::ByMove; |
| using testing::Eq; |
| using testing::Mock; |
| using testing::NiceMock; |
| using testing::Return; |
| using testing::StrictMock; |
| using ShouldShowAction = ManualFillingController::ShouldShowAction; |
| |
| class TestPasswordManagerClient |
| : public password_manager::StubPasswordManagerClient { |
| public: |
| TestPasswordManagerClient(); |
| ~TestPasswordManagerClient() override; |
| |
| password_manager::PasswordStoreInterface* GetProfilePasswordStore() |
| const override; |
| |
| MOCK_METHOD((const password_manager::PasswordManager*), |
| GetPasswordManager, |
| (), |
| (const, override)); |
| |
| private: |
| scoped_refptr<MockPasswordStoreInterface> mock_password_store_; |
| }; |
| |
| TestPasswordManagerClient::TestPasswordManagerClient() { |
| mock_password_store_ = new MockPasswordStoreInterface(); |
| } |
| |
| TestPasswordManagerClient::~TestPasswordManagerClient() = default; |
| |
| password_manager::PasswordStoreInterface* |
| TestPasswordManagerClient::GetProfilePasswordStore() const { |
| return mock_password_store_.get(); |
| } |
| |
| // Mock modal dialog view used to bypass the need of a valid top level window. |
| class MockPasswordGenerationDialogView |
| : public PasswordGenerationDialogViewInterface { |
| public: |
| MockPasswordGenerationDialogView() = default; |
| |
| MOCK_METHOD(void, |
| Show, |
| (std::u16string&, |
| base::WeakPtr<password_manager::ContentPasswordManagerDriver>, |
| PasswordGenerationType), |
| (override)); |
| MOCK_METHOD(void, Destroy, (), ()); |
| |
| MockPasswordGenerationDialogView(const MockPasswordGenerationDialogView&) = |
| delete; |
| MockPasswordGenerationDialogView& operator=( |
| const MockPasswordGenerationDialogView&) = delete; |
| |
| ~MockPasswordGenerationDialogView() override { Destroy(); } |
| }; |
| |
| PasswordGenerationUIData GetTestGenerationUIData1() { |
| PasswordGenerationUIData data; |
| |
| data.form_data.action = GURL("http://www.example1.com/accounts/Login"); |
| data.form_data.url = GURL("http://www.example1.com/accounts/LoginAuth"); |
| |
| data.generation_element = u"testelement1"; |
| data.max_length = 10; |
| |
| return data; |
| } |
| |
| PasswordGenerationUIData GetTestGenerationUIData2() { |
| PasswordGenerationUIData data; |
| |
| data.form_data.action = GURL("http://www.example2.com/accounts/Login"); |
| data.form_data.url = GURL("http://www.example2.com/accounts/LoginAuth"); |
| |
| data.generation_element = u"testelement2"; |
| data.max_length = 10; |
| |
| return data; |
| } |
| |
| MATCHER_P(PointsToSameAddress, expected, "") { |
| return arg.get() == expected; |
| } |
| |
| } // namespace |
| |
| class PasswordGenerationControllerTest |
| : public ChromeRenderViewHostTestHarness { |
| public: |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| |
| test_pwd_manager_client_ = std::make_unique<TestPasswordManagerClient>(); |
| password_manager_ = std::make_unique<password_manager::PasswordManager>( |
| test_pwd_manager_client_.get()); |
| ON_CALL(*test_pwd_manager_client_, GetPasswordManager()) |
| .WillByDefault(Return(password_manager_.get())); |
| |
| PasswordGenerationControllerImpl::CreateForWebContentsForTesting( |
| web_contents(), test_pwd_manager_client_.get(), |
| mock_manual_filling_controller_.AsWeakPtr(), |
| mock_dialog_factory_.Get()); |
| |
| password_manager_driver_ = std::make_unique<ContentPasswordManagerDriver>( |
| main_rfh(), test_pwd_manager_client_.get(), &test_autofill_client_); |
| another_password_manager_driver_ = |
| std::make_unique<ContentPasswordManagerDriver>( |
| main_rfh(), test_pwd_manager_client_.get(), &test_autofill_client_); |
| |
| // TODO(crbug.com/969051): Remove once kAutofillKeyboardAccessory is |
| // enabled. |
| password_autofill_manager_ = |
| std::make_unique<password_manager::PasswordAutofillManager>( |
| password_manager_driver_.get(), &test_autofill_client_, |
| test_pwd_manager_client_.get()); |
| |
| mock_dialog_ = |
| std::make_unique<NiceMock<MockPasswordGenerationDialogView>>(); |
| |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->FocusedInputChanged( |
| FocusedFieldType::kFillablePasswordField, |
| base::AsWeakPtr(password_manager_driver_.get())); |
| } |
| |
| PasswordGenerationController* controller() { |
| return PasswordGenerationControllerImpl::FromWebContents(web_contents()); |
| } |
| |
| base::WeakPtr<password_manager::ContentPasswordManagerDriver> |
| active_driver() { |
| return base::AsWeakPtr(password_manager_driver_.get()); |
| } |
| |
| base::WeakPtr<password_manager::ContentPasswordManagerDriver> |
| non_active_driver() { |
| return base::AsWeakPtr(another_password_manager_driver_.get()); |
| } |
| |
| const base::MockCallback< |
| PasswordGenerationControllerImpl::CreateDialogFactory>& |
| mock_dialog_factory() { |
| return mock_dialog_factory_; |
| } |
| |
| protected: |
| StrictMock<MockManualFillingController> mock_manual_filling_controller_; |
| |
| std::unique_ptr<ContentPasswordManagerDriver> password_manager_driver_; |
| std::unique_ptr<ContentPasswordManagerDriver> |
| another_password_manager_driver_; |
| std::unique_ptr<NiceMock<MockPasswordGenerationDialogView>> mock_dialog_; |
| |
| private: |
| NiceMock< |
| base::MockCallback<PasswordGenerationControllerImpl::CreateDialogFactory>> |
| mock_dialog_factory_; |
| std::unique_ptr<password_manager::PasswordManager> password_manager_; |
| std::unique_ptr<password_manager::PasswordAutofillManager> |
| password_autofill_manager_; |
| std::unique_ptr<TestPasswordManagerClient> test_pwd_manager_client_; |
| autofill::TestAutofillClient test_autofill_client_; |
| }; |
| |
| TEST_F(PasswordGenerationControllerTest, IsNotRecreatedForSameWebContents) { |
| PasswordGenerationController* initial_controller = |
| PasswordGenerationControllerImpl::FromWebContents(web_contents()); |
| EXPECT_NE(nullptr, initial_controller); |
| PasswordGenerationControllerImpl::CreateForWebContents(web_contents()); |
| EXPECT_EQ(PasswordGenerationControllerImpl::FromWebContents(web_contents()), |
| initial_controller); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, RelaysAutomaticGenerationAvailable) { |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(true), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->OnAutomaticGenerationAvailable( |
| active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20)); |
| } |
| |
| // Tests that if AutomaticGenerationAvailable is called for different |
| // password forms, the form and field signatures used for password generation |
| // are updated. |
| TEST_F(PasswordGenerationControllerTest, |
| UpdatesSignaturesForDifferentGenerationForms) { |
| // Called twice for different forms. |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(true), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)) |
| .Times(2); |
| controller()->OnAutomaticGenerationAvailable( |
| active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20)); |
| PasswordGenerationUIData new_ui_data = GetTestGenerationUIData2(); |
| controller()->OnAutomaticGenerationAvailable(active_driver(), new_ui_data, |
| gfx::RectF(100, 20)); |
| |
| autofill::FormSignature form_signature = |
| autofill::CalculateFormSignature(new_ui_data.form_data); |
| autofill::FieldSignature field_signature = |
| autofill::CalculateFieldSignatureByNameAndType( |
| new_ui_data.generation_element, "password"); |
| EXPECT_EQ(controller()->get_form_signature_for_testing(), form_signature); |
| EXPECT_EQ(controller()->get_field_signature_for_testing(), field_signature); |
| |
| NiceMock<MockPasswordGenerationDialogView>* raw_dialog_view = |
| mock_dialog_.get(); |
| EXPECT_CALL(mock_dialog_factory(), Run) |
| .WillOnce(Return(ByMove(std::move(mock_dialog_)))); |
| EXPECT_CALL(*raw_dialog_view, |
| Show(_, PointsToSameAddress(password_manager_driver_.get()), |
| PasswordGenerationType::kAutomatic)); |
| controller()->OnGenerationRequested(PasswordGenerationType::kAutomatic); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| RecordsGeneratedPasswordAcceptedAutomatic) { |
| base::HistogramTester histogram_tester; |
| |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(true), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->OnAutomaticGenerationAvailable( |
| active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20)); |
| |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->GeneratedPasswordAccepted(u"t3stp@ssw0rd", active_driver(), |
| PasswordGenerationType::kAutomatic); |
| |
| histogram_tester.ExpectUniqueSample( |
| "KeyboardAccessory.GenerationDialogChoice.Automatic", |
| GenerationDialogChoice::kAccepted, 1); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| RecordsGeneratedPasswordRejectedAutomatic) { |
| base::HistogramTester histogram_tester; |
| |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->GeneratedPasswordRejected(PasswordGenerationType::kAutomatic); |
| |
| histogram_tester.ExpectUniqueSample( |
| "KeyboardAccessory.GenerationDialogChoice.Automatic", |
| GenerationDialogChoice::kRejected, 1); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| RecordsGeneratedPasswordAcceptedManual) { |
| base::HistogramTester histogram_tester; |
| |
| controller()->OnGenerationRequested(PasswordGenerationType::kManual); |
| |
| EXPECT_CALL(mock_dialog_factory(), Run) |
| .WillOnce(Return(ByMove(std::move(mock_dialog_)))); |
| controller()->ShowManualGenerationDialog(password_manager_driver_.get(), |
| GetTestGenerationUIData1()); |
| |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->GeneratedPasswordAccepted(u"t3stp@ssw0rd", active_driver(), |
| PasswordGenerationType::kManual); |
| |
| histogram_tester.ExpectUniqueSample( |
| "KeyboardAccessory.GenerationDialogChoice.Manual", |
| GenerationDialogChoice::kAccepted, 1); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| RecordsGeneratedPasswordRejectedManual) { |
| base::HistogramTester histogram_tester; |
| |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->GeneratedPasswordRejected(PasswordGenerationType::kManual); |
| |
| histogram_tester.ExpectUniqueSample( |
| "KeyboardAccessory.GenerationDialogChoice.Manual", |
| GenerationDialogChoice::kRejected, 1); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| SetActiveFrameOnAutomaticGenerationAvailable) { |
| // TODO(crbug.com/1421753): Refactor PasswordGenerationController so that |
| // OnAccessoryActionAvailabilityChanged would be called only once. Right now |
| // it's called twice: the first call resets the manual filling controller |
| // status and the second one sets it according to the focused input. |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| _, autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)) |
| .Times(AtMost(2)); |
| |
| controller()->OnAutomaticGenerationAvailable( |
| non_active_driver(), GetTestGenerationUIData2(), gfx::RectF(100, 20)); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| ResetStateWhenFocusChangesToNonPassword) { |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| |
| controller()->FocusedInputChanged(FocusedFieldType::kFillableUsernameField, |
| active_driver()); |
| EXPECT_FALSE(controller()->GetActiveFrameDriver()); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| ResetStateWhenFocusChangesToOtherFramePassword) { |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| |
| controller()->FocusedInputChanged(FocusedFieldType::kFillablePasswordField, |
| non_active_driver()); |
| EXPECT_EQ(another_password_manager_driver_.get(), |
| controller()->GetActiveFrameDriver().get()); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, HidesDialogWhenFocusChanges) { |
| controller()->OnGenerationRequested(PasswordGenerationType::kManual); |
| |
| NiceMock<MockPasswordGenerationDialogView>* raw_dialog_view = |
| mock_dialog_.get(); |
| EXPECT_CALL(mock_dialog_factory(), Run) |
| .WillOnce(Return(ByMove(std::move(mock_dialog_)))); |
| EXPECT_CALL(*raw_dialog_view, |
| Show(_, PointsToSameAddress(password_manager_driver_.get()), |
| PasswordGenerationType::kManual)); |
| controller()->ShowManualGenerationDialog(password_manager_driver_.get(), |
| GetTestGenerationUIData1()); |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| EXPECT_CALL(*raw_dialog_view, Destroy()); |
| controller()->FocusedInputChanged(FocusedFieldType::kFillableUsernameField, |
| non_active_driver()); |
| Mock::VerifyAndClearExpectations(raw_dialog_view); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, ShowManualDialogForActiveFrame) { |
| controller()->OnGenerationRequested(PasswordGenerationType::kManual); |
| |
| NiceMock<MockPasswordGenerationDialogView>* raw_dialog_view = |
| mock_dialog_.get(); |
| EXPECT_CALL(mock_dialog_factory(), Run) |
| .WillOnce(Return(ByMove(std::move(mock_dialog_)))); |
| EXPECT_CALL(*raw_dialog_view, |
| Show(_, PointsToSameAddress(password_manager_driver_.get()), |
| PasswordGenerationType::kManual)); |
| controller()->ShowManualGenerationDialog(password_manager_driver_.get(), |
| GetTestGenerationUIData1()); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| RejectShowManualDialogForNonActiveFrame) { |
| EXPECT_CALL(mock_dialog_factory(), Run).Times(0); |
| controller()->ShowManualGenerationDialog( |
| another_password_manager_driver_.get(), GetTestGenerationUIData1()); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, DontShowDialogIfAlreadyShown) { |
| controller()->OnGenerationRequested(PasswordGenerationType::kManual); |
| |
| NiceMock<MockPasswordGenerationDialogView>* raw_dialog_view = |
| mock_dialog_.get(); |
| EXPECT_CALL(mock_dialog_factory(), Run) |
| .WillOnce(Return(ByMove(std::move(mock_dialog_)))); |
| |
| EXPECT_CALL(*raw_dialog_view, |
| Show(_, PointsToSameAddress(password_manager_driver_.get()), |
| PasswordGenerationType::kManual)); |
| controller()->ShowManualGenerationDialog(password_manager_driver_.get(), |
| GetTestGenerationUIData1()); |
| |
| EXPECT_CALL(mock_dialog_factory(), Run).Times(0); |
| controller()->ShowManualGenerationDialog(password_manager_driver_.get(), |
| GetTestGenerationUIData1()); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, DontShowManualDialogIfFocusChanged) { |
| controller()->OnGenerationRequested(PasswordGenerationType::kManual); |
| |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(false), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)); |
| controller()->FocusedInputChanged(FocusedFieldType::kFillablePasswordField, |
| non_active_driver()); |
| EXPECT_CALL(mock_dialog_factory(), Run).Times(0); |
| controller()->ShowManualGenerationDialog(password_manager_driver_.get(), |
| GetTestGenerationUIData1()); |
| } |
| |
| TEST_F(PasswordGenerationControllerTest, |
| DoesNotCallKeyboardAccessoryWhenGenerationBottomSheetRequired) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| password_manager::features::kPasswordGenerationBottomSheet); |
| |
| // Keyboard accessory shouldn't be called. |
| EXPECT_CALL(mock_manual_filling_controller_, |
| OnAccessoryActionAvailabilityChanged( |
| ShouldShowAction(true), |
| autofill::AccessoryAction::GENERATE_PASSWORD_AUTOMATIC)) |
| .Times(0); |
| controller()->OnAutomaticGenerationAvailable( |
| active_driver(), GetTestGenerationUIData1(), gfx::RectF(100, 20)); |
| controller()->HideBottomSheetIfNeeded(); |
| } |