chromium/chrome/browser/password_manager/android/cred_man_controller_unittest.cc

// Copyright 2023 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/cred_man_controller.h"

#include <memory>

#include "base/memory/weak_ptr.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "components/device_reauth/mock_device_authenticator.h"
#include "components/password_manager/content/browser/mock_keyboard_replacing_surface_visibility_controller.h"
#include "components/password_manager/core/browser/mock_password_credential_filler.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/webauthn/android/cred_man_support.h"
#include "components/webauthn/android/webauthn_cred_man_delegate.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace password_manager {

using testing::_;
using testing::Return;

using webauthn::CredManSupport;
using webauthn::WebAuthnCredManDelegate;
using ToShowVirtualKeyboard = PasswordManagerDriver::ToShowVirtualKeyboard;

class MockPasswordManagerClient
    : public password_manager::StubPasswordManagerClient {
 public:
  MOCK_METHOD(bool,
              IsReauthBeforeFillingRequired,
              (device_reauth::DeviceAuthenticator*),
              (override));
  MOCK_METHOD(std::unique_ptr<device_reauth::DeviceAuthenticator>,
              GetDeviceAuthenticator,
              (),
              (override));
};

class CredManControllerTest : public testing::Test {
 public:
  void SetUp() override {
    auto mock_authenticator =
        std::make_unique<device_reauth::MockDeviceAuthenticator>();
    device_authenticator_ = mock_authenticator.get();
    EXPECT_CALL(*password_client(), GetDeviceAuthenticator)
        .WillOnce(Return(testing::ByMove(std::move(mock_authenticator))))
        .RetiresOnSaturation();
    controller_ = std::make_unique<CredManController>(
        visibility_controller_.AsWeakPtr(), &password_client_);
    driver_ = std::make_unique<StubPasswordManagerDriver>();
    web_authn_cred_man_delegate_ =
        std::make_unique<WebAuthnCredManDelegate>(nullptr);
    WebAuthnCredManDelegate::override_cred_man_support_for_testing(
        CredManSupport::FULL_UNLESS_INAPPLICABLE);
  }

  std::unique_ptr<MockPasswordCredentialFiller> PrepareFiller() {
    auto filler = std::make_unique<MockPasswordCredentialFiller>();
    last_filler_ = filler.get();
    return filler;
  }

  CredManController& controller() { return *controller_.get(); }
  PasswordManagerDriver* driver() { return driver_.get(); }
  WebAuthnCredManDelegate* web_authn_cred_man_delegate() {
    return web_authn_cred_man_delegate_.get();
  }
  MockPasswordCredentialFiller& last_filler() {
    EXPECT_NE(last_filler_, nullptr)
        << "Call PrepareFiller to setup last_filler.";
    return *last_filler_.get();
  }
  MockKeyboardReplacingSurfaceVisibilityController& visibility_controller() {
    return visibility_controller_;
  }
  MockPasswordManagerClient* password_client() { return &password_client_; }
  device_reauth::MockDeviceAuthenticator* device_authenticator() {
    return device_authenticator_;
  }

 private:
  std::unique_ptr<CredManController> controller_;
  std::unique_ptr<StubPasswordManagerDriver> driver_;
  std::unique_ptr<WebAuthnCredManDelegate> web_authn_cred_man_delegate_;
  raw_ptr<MockPasswordCredentialFiller> last_filler_;
  MockKeyboardReplacingSurfaceVisibilityController visibility_controller_;
  MockPasswordManagerClient password_client_;
  raw_ptr<device_reauth::MockDeviceAuthenticator> device_authenticator_;
};

TEST_F(CredManControllerTest, DoesNotShowIfNonWebAuthnForm) {
  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();
  EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
  EXPECT_CALL(last_filler(), Dismiss(ToShowVirtualKeyboard(false)));
  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
                                 std::move(filler),
                                 /*frame_driver=*/nullptr,
                                 /*is_webauthn_form=*/false));
}

TEST_F(CredManControllerTest, DoesNotShowIfFeatureDisabled) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::DISABLED);
  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();
  EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
  EXPECT_CALL(last_filler(), Dismiss(ToShowVirtualKeyboard(false)));
  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
                                 std::move(filler),
                                 /*frame_driver=*/nullptr,
                                 /*is_webauthn_form=*/true));
}

TEST_F(CredManControllerTest, DoesNotShowIfGpmNotInCredMan) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::PARALLEL_WITH_FIDO_2);
  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();
  EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
  EXPECT_CALL(last_filler(), Dismiss(ToShowVirtualKeyboard(false)));
  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
                                 std::move(filler),
                                 /*frame_driver=*/nullptr,
                                 /*is_webauthn_form=*/true));
}

TEST_F(CredManControllerTest, DoesNotShowIfNoResults) {
  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();
  EXPECT_CALL(last_filler(), Dismiss(ToShowVirtualKeyboard(false)));

  base::MockCallback<base::RepeatingCallback<void(bool)>>
      mock_full_assertion_request;
  web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/false, mock_full_assertion_request.Get());

  EXPECT_CALL(visibility_controller(), SetVisible(_)).Times(0);
  EXPECT_FALSE(controller().Show(web_authn_cred_man_delegate(),
                                 std::move(filler),
                                 /*frame_driver=*/nullptr,
                                 /*is_webauthn_form=*/true));
}

TEST_F(CredManControllerTest, ShowIfResultsExist) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::FULL_UNLESS_INAPPLICABLE);
  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();

  base::MockCallback<base::RepeatingCallback<void(bool)>>
      mock_full_assertion_request;
  web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, mock_full_assertion_request.Get());

  EXPECT_CALL(visibility_controller(), SetVisible(_));
  EXPECT_TRUE(controller().Show(web_authn_cred_man_delegate(),
                                std::move(filler),
                                /*frame_driver=*/nullptr,
                                /*is_webauthn_form=*/true));
}

TEST_F(CredManControllerTest, Fill) {
  WebAuthnCredManDelegate::override_cred_man_support_for_testing(
      CredManSupport::FULL_UNLESS_INAPPLICABLE);
  base::HistogramTester uma_recorder;
  const std::u16string kUsername = u"test_user";
  const std::u16string kPassword = u"38kAy5Er1Sp0r38";

  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();

  base::MockCallback<base::RepeatingCallback<void(bool)>>
      mock_full_assertion_request;
  web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, mock_full_assertion_request.Get());

  EXPECT_TRUE(controller().Show(web_authn_cred_man_delegate(),
                                std::move(filler),
                                /*frame_driver=*/nullptr,
                                /*is_webauthn_form=*/true));

  ON_CALL(last_filler(), ShouldTriggerSubmission()).WillByDefault(Return(true));
  EXPECT_CALL(last_filler(), FillUsernameAndPassword(kUsername, kPassword));
  web_authn_cred_man_delegate()->FillUsernameAndPassword(kUsername, kPassword);
  uma_recorder.ExpectBucketCount(
      "PasswordManager.CredMan.PasswordFormSubmissionTriggered", /*true*/ 1, 1);
}

TEST_F(CredManControllerTest, FillsPasswordIfReauthSuccessfull) {
  const std::u16string kUsername = u"test_user";
  const std::u16string kPassword = u"38kAy5Er1Sp0r38";

  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();

  base::MockCallback<base::RepeatingCallback<void(bool)>>
      mock_full_assertion_request;
  web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, mock_full_assertion_request.Get());

  ASSERT_TRUE(controller().Show(web_authn_cred_man_delegate(),
                                std::move(filler),
                                /*frame_driver=*/nullptr,
                                /*is_webauthn_form=*/true));

  ON_CALL(*password_client(), IsReauthBeforeFillingRequired)
      .WillByDefault(Return(true));
  EXPECT_CALL(*device_authenticator(), AuthenticateWithMessage)
      .WillOnce(base::test::RunOnceCallback<1>(/*auth_succeeded=*/true));
  EXPECT_CALL(last_filler(), FillUsernameAndPassword(kUsername, kPassword));

  web_authn_cred_man_delegate()->FillUsernameAndPassword(kUsername, kPassword);
}

TEST_F(CredManControllerTest, DoesNotFillIfReauthFailed) {
  const std::u16string kUsername = u"test_user";
  const std::u16string kPassword = u"38kAy5Er1Sp0r38";

  std::unique_ptr<MockPasswordCredentialFiller> filler = PrepareFiller();

  base::MockCallback<base::RepeatingCallback<void(bool)>>
      mock_full_assertion_request;
  web_authn_cred_man_delegate()->OnCredManConditionalRequestPending(
      /*has_results=*/true, mock_full_assertion_request.Get());

  ASSERT_TRUE(controller().Show(web_authn_cred_man_delegate(),
                                std::move(filler),
                                /*frame_driver=*/nullptr,
                                /*is_webauthn_form=*/true));

  ON_CALL(*password_client(), IsReauthBeforeFillingRequired)
      .WillByDefault(Return(true));
  EXPECT_CALL(*device_authenticator(), AuthenticateWithMessage)
      .WillOnce(base::test::RunOnceCallback<1>(/*auth_succeeded=*/false));
  EXPECT_CALL(last_filler(), FillUsernameAndPassword).Times(0);
  web_authn_cred_man_delegate()->FillUsernameAndPassword(kUsername, kPassword);
}

}  // namespace password_manager