chromium/chrome/browser/safe_browsing/chrome_password_protection_service_unittest.cc

// Copyright 2017 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/safe_browsing/chrome_password_protection_service.h"

#include <memory>
#include <utility>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h"
#include "chrome/browser/password_manager/account_password_store_factory.h"
#include "chrome/browser/password_manager/profile_password_store_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/chrome_safe_browsing_blocking_page_factory.h"
#include "chrome/browser/safe_browsing/chrome_ui_manager_delegate.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/signin/test_signin_client_builder.h"
#include "chrome/browser/sync/user_event_service_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/password_manager/core/browser/hash_password_manager.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_manager_test_utils.h"
#include "components/password_manager/core/browser/password_reuse_detector.h"
#include "components/password_manager/core/browser/password_store/mock_password_store_interface.h"
#include "components/password_manager/core/browser/split_stores_and_local_upm.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/safe_browsing/content/browser/password_protection/password_protection_commit_deferring_condition.h"
#include "components/safe_browsing/content/browser/password_protection/password_protection_request_content.h"
#include "components/safe_browsing/content/browser/ui_manager.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/browser/verdict_cache_manager.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/utils.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/model/data_type_controller_delegate.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/sync_user_events/fake_user_event_service.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/mock_navigation_handle.h"
#include "net/http/http_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#include "chrome/browser/password_manager/android/mock_password_checkup_launcher_helper.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "components/sync/test/test_sync_service.h"
#endif

// All tests related to extension is disabled on Android, because enterprise
// reporting extension is not supported.
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
#include "chrome/browser/safe_browsing/test_extension_event_observer.h"
#include "chrome/common/extensions/api/safe_browsing_private.h"
#include "extensions/browser/test_event_router.h"
#endif

MatchingReusedCredential;
UserEventSpecifics;
GaiaPasswordReuse;
GaiaPasswordCaptured;
PasswordReuseDialogInteraction;
PasswordReuseEvent;
PasswordReuseLookup;
_;
Return;
WithArg;

#if !BUILDFLAG(IS_ANDROID)
OnPolicySpecifiedPasswordReuseDetected;
OnPolicySpecifiedPasswordChanged;
#endif

class MockSecurityEventRecorder : public SecurityEventRecorder {};

namespace safe_browsing {

namespace {

const char kPhishingURL[] =;
const char kTestEmail[] =;
const char kUserName[] =;
const char kRedirectURL[] =;
#if !BUILDFLAG(IS_ANDROID)
const char kPasswordReuseURL[] =;
const char kTestGmail[] =;
#endif

BrowserContextKeyedServiceFactory::TestingFactory
GetFakeUserEventServiceFactory() {}

constexpr struct {} kTestCasesWithoutVerdict[]{};

#if BUILDFLAG(IS_ANDROID)
std::unique_ptr<KeyedService> CreateTestSyncService(
    content::BrowserContext* context) {
  return std::make_unique<syncer::TestSyncService>();
}
#endif

}  // namespace

class MockChromePasswordProtectionService
    : public ChromePasswordProtectionService {};

class ChromePasswordProtectionServiceTest
    : public ChromeRenderViewHostTestHarness {};

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyUserPopulationForPasswordOnFocusPing) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyUserPopulationForSavedPasswordEntryPing) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyUserPopulationForSyncPasswordEntryPing) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPingingIsSkippedIfMatchEnterpriseAllowlist) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPersistPhishedSavedPasswordCredential) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyRemovePhishedSavedPasswordCredential) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyCanSendSamplePing) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyGetOrganizationTypeGmail) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyGetOrganizationTypeGSuite) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyUpdateSecurityState) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordReuseUserEventNotRecordedDueToIncognito) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordReuseDetectedUserEventRecorded) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordReuseDetectedSecurityEventRecorded) {}

// The following tests are disabled on Android, because password capture events
// are not enabled on Android.
#if !BUILDFLAG(IS_ANDROID)
// Check that the PasswordCapturedEvent timer is set for 1 min if password
// hash is saved and no timer pref is set yet.
TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordCaptureEventScheduledOnStartup) {}

// Check that the timer is set for prescribed time based on pref.
TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordCaptureEventScheduledFromPref) {}

// Check that we do log the event
TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordCaptureEventRecorded) {}

// Check that we reschedule after logging.
TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordCaptureEventReschedules) {}
#endif

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyPasswordReuseLookupUserEventRecorded) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyGetDefaultChangePasswordURL) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyGetEnterprisePasswordURL) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyNavigationDuringPasswordOnFocusPingNotBlocked) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyNavigationDuringPasswordReusePingDeferred) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyNavigationDuringModalWarningDeferred) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyCommitDeferringConditionRemovedWhenNavigationHandleIsGone) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyUnhandledSyncPasswordReuseUponClearHistoryDeletion) {}

// The following tests are disabled on Android, because enterprise reporting
// extension is not supported.
#if !BUILDFLAG(IS_ANDROID)
TEST_F(ChromePasswordProtectionServiceTest,
       VerifyOnPolicySpecifiedPasswordChangedEvent) {}

TEST_F(
    ChromePasswordProtectionServiceTest,
    VerifyTriggerOnPolicySpecifiedPasswordReuseDetectedForEnterprisePasswordWithAlertMode) {}

TEST_F(
    ChromePasswordProtectionServiceTest,
    VerifyTriggerOnPolicySpecifiedPasswordReuseDetectedForEnterprisePasswordOnChromeExtension) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyTriggerOnPolicySpecifiedPasswordReuseDetectedForGsuiteUser) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyTriggerOnPolicySpecifiedPasswordReuseDetectedForGmailUser) {}
#endif

TEST_F(ChromePasswordProtectionServiceTest, VerifyGetWarningDetailTextSaved) {}

TEST_F(ChromePasswordProtectionServiceTest,
       VerifyGetWarningDetailTextEnterprise) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyGetWarningDetailTextGmail) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyCanShowInterstitial) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifySendsPingForAboutBlank) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyGetPingNotSentReason) {}

TEST_F(ChromePasswordProtectionServiceTest, VerifyPageLoadToken) {}

namespace {

class ChromePasswordProtectionServiceWithAccountPasswordStoreTest
    : public ChromePasswordProtectionServiceTest {};

TEST_F(ChromePasswordProtectionServiceWithAccountPasswordStoreTest,
       VerifyPersistPhishedSavedPasswordCredential) {}

TEST_F(ChromePasswordProtectionServiceWithAccountPasswordStoreTest,
       VerifyRemovePhishedSavedPasswordCredential) {}

#if BUILDFLAG(IS_ANDROID)
class PasswordCheckupWithPhishGuardTest
    : public ChromePasswordProtectionServiceTest {
 protected:
  void SetUpSyncService(bool is_syncing_passwords) {
    // Setting up the syncing account.
    CoreAccountInfo account;
    account.email = profile()->GetProfileUserName();
    sync_service_ = static_cast<syncer::TestSyncService*>(
        SyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
            profile(), base::BindRepeating(&CreateTestSyncService)));
    sync_service_->SetSignedIn(signin::ConsentLevel::kSync, account);
    ASSERT_TRUE(sync_service_->IsSyncFeatureEnabled());
    sync_service_->GetUserSettings()->SetSelectedType(
        syncer::UserSelectableType::kPasswords, is_syncing_passwords);
  }

  // Simulates clicking "Change Password" button on the modal dialog.
  void SimulateChangePasswordDialogAction(bool is_syncing) {
    ReusedPasswordAccountType password_account_type;
    password_account_type.set_account_type(
        ReusedPasswordAccountType::SAVED_PASSWORD);
    password_account_type.set_is_account_syncing(is_syncing);

    service_->OnUserAction(
        web_contents(), password_account_type, RequestOutcome::UNKNOWN,
        LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, "unused_token",
        WarningUIType::MODAL_DIALOG, WarningAction::CHANGE_PASSWORD);
  }

  raw_ptr<syncer::TestSyncService> sync_service_ = nullptr;
};

class PasswordCheckupWithPhishGuardAfterPasswordStoreSplitAndroidTest
    : public PasswordCheckupWithPhishGuardTest {
 public:
  void SetUp() override {
    // Override the GMS version to be big enough for local UPM support, so these
    // tests still pass in bots with an outdated version.
    base::android::BuildInfo::GetInstance()->set_gms_version_code_for_test(
        base::NumberToString(password_manager::GetLocalUpmMinGmsVersion()));
    PasswordCheckupWithPhishGuardTest::SetUp();
  }
};

TEST_F(PasswordCheckupWithPhishGuardAfterPasswordStoreSplitAndroidTest,
       VerifyPhishGuardDialogOpensPasswordCheckupForAccountStoreSyncing) {
  service_->ConfigService(/*is_incognito=*/false,
                          /*is_extended_reporting=*/true);
  std::vector<password_manager::MatchingReusedCredential> credentials = {
      {"http://example.test", u"username",
       password_manager::PasswordForm::Store::kAccountStore}};
  service_->set_saved_passwords_matching_reused_credentials(credentials);

  SetUpSyncService(/*is_syncing_passwords=*/true);

  EXPECT_CALL(
      *mock_checkup_launcher_,
      LaunchCheckupOnDevice(
          _, profile(), web_contents()->GetTopLevelNativeWindow(),
          password_manager::PasswordCheckReferrerAndroid::kPhishedWarningDialog,
          TestingProfile::kDefaultProfileUserName));

  SimulateChangePasswordDialogAction(/*is_syncing=*/true);
}

TEST_F(PasswordCheckupWithPhishGuardAfterPasswordStoreSplitAndroidTest,
       VerifyPhishGuardDialogOpensPasswordCheckupForProfileStoreSyncing) {
  service_->ConfigService(/*is_incognito=*/false,
                          /*is_extended_reporting=*/true);
  std::vector<password_manager::MatchingReusedCredential> credentials = {
      {"http://example.test", u"username",
       password_manager::PasswordForm::Store::kProfileStore}};
  service_->set_saved_passwords_matching_reused_credentials(credentials);

  SetUpSyncService(/*is_syncing_passwords=*/true);

  EXPECT_CALL(
      *mock_checkup_launcher_,
      LaunchCheckupOnDevice(
          _, profile(), web_contents()->GetTopLevelNativeWindow(),
          password_manager::PasswordCheckReferrerAndroid::kPhishedWarningDialog,
          /*account=*/""));

  SimulateChangePasswordDialogAction(/*is_syncing=*/true);
}

TEST_F(PasswordCheckupWithPhishGuardAfterPasswordStoreSplitAndroidTest,
       VerifyPhishGuardDialogOpensPasswordCheckupForProfileStoreNotSyncing) {
  service_->ConfigService(/*is_incognito=*/false,
                          /*is_extended_reporting=*/true);
  std::vector<password_manager::MatchingReusedCredential> credentials = {
      {"http://example.test", u"username",
       password_manager::PasswordForm::Store::kProfileStore}};
  service_->set_saved_passwords_matching_reused_credentials(credentials);

  SetUpSyncService(/*is_syncing_passwords=*/false);

  EXPECT_CALL(
      *mock_checkup_launcher_,
      LaunchCheckupOnDevice(
          _, profile(), web_contents()->GetTopLevelNativeWindow(),
          password_manager::PasswordCheckReferrerAndroid::kPhishedWarningDialog,
          /*account=*/""));

  SimulateChangePasswordDialogAction(/*is_syncing=*/false);
}

TEST_F(PasswordCheckupWithPhishGuardAfterPasswordStoreSplitAndroidTest,
       VerifyPhishGuardDialogOpensSafetyCheckMenuForBothStoresSyncing) {
  service_->ConfigService(/*is_incognito=*/false,
                          /*is_extended_reporting=*/true);
  std::vector<password_manager::MatchingReusedCredential> credentials = {
      {"http://example.test", u"username",
       password_manager::PasswordForm::Store::kProfileStore},
      {"http://2.example.test", u"username",
       password_manager::PasswordForm::Store::kAccountStore}};
  service_->set_saved_passwords_matching_reused_credentials(credentials);

  SetUpSyncService(/*is_syncing_passwords=*/true);

  EXPECT_CALL(*mock_checkup_launcher_,
              LaunchSafetyCheck(_, web_contents()->GetTopLevelNativeWindow()));

  SimulateChangePasswordDialogAction(/*is_syncing=*/true);
}

class PasswordCheckupWithPhishGuardUPMBeforeStoreSplitAndroidTest
    : public PasswordCheckupWithPhishGuardTest {
 public:
  void SetUp() override {
    // Force split stores to be off by faking an outdated GmsCore version.
    base::android::BuildInfo::GetInstance()->set_gms_version_code_for_test("0");
    PasswordCheckupWithPhishGuardTest::SetUp();
  }
};

TEST_F(
    PasswordCheckupWithPhishGuardUPMBeforeStoreSplitAndroidTest,
    VerifyPhishGuardDialogOpensPasswordCheckupEmptyAccountForNonSyncingUser) {
  service_->ConfigService(/*is_incognito=*/false,
                          /*is_extended_reporting=*/true);
  std::vector<password_manager::MatchingReusedCredential> credentials = {
      {"http://example.test", u"username",
       password_manager::PasswordForm::Store::kProfileStore}};
  service_->set_saved_passwords_matching_reused_credentials(credentials);

  SetUpSyncService(/*is_syncing_passwords=*/false);

  EXPECT_CALL(
      *mock_checkup_launcher_,
      LaunchCheckupOnDevice(
          _, profile(), web_contents()->GetTopLevelNativeWindow(),
          password_manager::PasswordCheckReferrerAndroid::kPhishedWarningDialog,
          /*account=*/""));

  SimulateChangePasswordDialogAction(/*is_syncing=*/false);
}

TEST_F(PasswordCheckupWithPhishGuardUPMBeforeStoreSplitAndroidTest,
       VerifyPhishGuardDialogOpensPasswordCheckupWithAnAccountForSyncingUser) {
  service_->ConfigService(/*is_incognito=*/false,
                          /*is_extended_reporting=*/true);
  std::vector<password_manager::MatchingReusedCredential> credentials = {
      {"http://example.test", u"username",
       password_manager::PasswordForm::Store::kProfileStore}};
  service_->set_saved_passwords_matching_reused_credentials(credentials);

  SetUpSyncService(/*is_syncing_passwords=*/true);

  EXPECT_CALL(
      *mock_checkup_launcher_,
      LaunchCheckupOnDevice(
          _, profile(), web_contents()->GetTopLevelNativeWindow(),
          password_manager::PasswordCheckReferrerAndroid::kPhishedWarningDialog,
          TestingProfile::kDefaultProfileUserName));

  SimulateChangePasswordDialogAction(/*is_syncing=*/true);
}
#endif

}  // namespace

}  // namespace safe_browsing