blob: 4518cd89372704418c6d392f6d906f1d973048da [file] [log] [blame]
// 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 "chrome/browser/ui/webui/settings/safety_check_handler.h"
#include <string>
#include <unordered_map>
#include "base/bind.h"
#include "base/optional.h"
#include "base/strings/strcat.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/strong_alias.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h"
#include "chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h"
#include "chrome/browser/extensions/test_extension_service.h"
#include "chrome/browser/ui/webui/help/test_version_updater.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/api/passwords_private.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/crx_file/id_util.h"
#include "components/password_manager/core/browser/bulk_leak_check_service.h"
#include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h"
#include "components/password_manager/core/browser/test_password_store.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safety_check/test_update_check_helper.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/version_info/version_info.h"
#include "content/public/test/test_web_ui.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_controller_impl_win.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/chromeos/devicetype_utils.h"
#endif
#if defined(OS_MAC)
#include "base/mac/mac_util.h"
#endif
// Components for building event strings.
constexpr char kParent[] = "parent";
constexpr char kUpdates[] = "updates";
constexpr char kPasswords[] = "passwords";
constexpr char kSafeBrowsing[] = "safe-browsing";
constexpr char kExtensions[] = "extensions";
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
constexpr char kChromeCleaner[] = "chrome-cleaner";
#endif
namespace {
using Enabled = base::StrongAlias<class EnabledTag, bool>;
using UserCanDisable = base::StrongAlias<class UserCanDisableTag, bool>;
class TestingSafetyCheckHandler : public SafetyCheckHandler {
public:
using SafetyCheckHandler::AllowJavascript;
using SafetyCheckHandler::DisallowJavascript;
using SafetyCheckHandler::set_web_ui;
using SafetyCheckHandler::SetTimestampDelegateForTesting;
using SafetyCheckHandler::SetVersionUpdaterForTesting;
TestingSafetyCheckHandler(
std::unique_ptr<safety_check::UpdateCheckHelper> update_helper,
std::unique_ptr<VersionUpdater> version_updater,
password_manager::BulkLeakCheckService* leak_service,
extensions::PasswordsPrivateDelegate* passwords_delegate,
extensions::ExtensionPrefs* extension_prefs,
extensions::ExtensionServiceInterface* extension_service,
std::unique_ptr<TimestampDelegate> timestamp_delegate)
: SafetyCheckHandler(std::move(update_helper),
std::move(version_updater),
leak_service,
passwords_delegate,
extension_prefs,
extension_service,
std::move(timestamp_delegate)) {}
};
class TestDestructionVersionUpdater : public TestVersionUpdater {
public:
~TestDestructionVersionUpdater() override { destructor_invoked_ = true; }
void CheckForUpdate(StatusCallback callback, PromoteCallback) override {}
static bool GetDestructorInvoked() { return destructor_invoked_; }
private:
static bool destructor_invoked_;
};
class TestTimestampDelegate : public TimestampDelegate {
public:
base::Time GetSystemTime() override {
// 1 second before midnight Dec 31st 2020, so that -(24h-1s) is still on the
// same day. This test time is hard coded to prevent DST flakiness, see
// crbug.com/1066576.
return base::Time::FromDoubleT(1609459199).LocalMidnight() -
base::TimeDelta::FromSeconds(1);
}
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
base::Time FetchChromeCleanerScanCompletionTimestamp() override {
// 2 seconds before midnight Dec 31st 2020.
return base::Time::FromDoubleT(1609459199).LocalMidnight() -
base::TimeDelta::FromSeconds(2);
}
#endif
};
bool TestDestructionVersionUpdater::destructor_invoked_ = false;
class TestPasswordsDelegate : public extensions::TestPasswordsPrivateDelegate {
public:
TestPasswordsDelegate() { store_->Init(/*prefs=*/nullptr); }
void TearDown() {
store_->ShutdownOnUIThread();
// Needs to be invoked in the test's TearDown() - before the destructor.
base::RunLoop().RunUntilIdle();
}
void SetBulkLeakCheckService(
password_manager::BulkLeakCheckService* leak_service) {
leak_service_ = leak_service;
}
void SetNumCompromisedCredentials(int compromised_password_count) {
compromised_password_count_ = compromised_password_count;
}
void SetNumWeakCredentials(int weak_password_count) {
weak_password_count_ = weak_password_count;
}
void SetPasswordCheckState(
extensions::api::passwords_private::PasswordCheckState state) {
state_ = state;
}
void SetProgress(int done, int total) {
done_ = done;
total_ = total;
}
void InvokeOnCompromisedCredentialsChanged() {
// Credentials have to be unique, so the callback is always invoked.
store_->AddCompromisedCredentials(password_manager::CompromisedCredentials(
"test.com",
base::ASCIIToUTF16("test" +
base::NumberToString(test_credential_counter_++)),
base::Time(), password_manager::CompromiseType::kLeaked,
password_manager::IsMuted(false)));
base::RunLoop().RunUntilIdle();
}
std::vector<extensions::api::passwords_private::InsecureCredential>
GetCompromisedCredentials() override {
std::vector<extensions::api::passwords_private::InsecureCredential>
compromised(compromised_password_count_);
for (int i = 0; i < compromised_password_count_; ++i) {
compromised[i].username = "test" + base::NumberToString(i);
}
return compromised;
}
std::vector<extensions::api::passwords_private::InsecureCredential>
GetWeakCredentials() override {
std::vector<extensions::api::passwords_private::InsecureCredential> weak(
weak_password_count_);
for (int i = 0; i < weak_password_count_; ++i) {
weak[i].username = "test" + base::NumberToString(i);
}
return weak;
}
extensions::api::passwords_private::PasswordCheckStatus
GetPasswordCheckStatus() override {
extensions::api::passwords_private::PasswordCheckStatus status;
status.state = state_;
if (total_ != 0) {
status.already_processed = std::make_unique<int>(done_);
status.remaining_in_queue = std::make_unique<int>(total_ - done_);
}
return status;
}
password_manager::InsecureCredentialsManager* GetInsecureCredentialsManager()
override {
return &credentials_manager_;
}
private:
password_manager::BulkLeakCheckService* leak_service_ = nullptr;
int compromised_password_count_ = 0;
int weak_password_count_ = 0;
int done_ = 0;
int total_ = 0;
int test_credential_counter_ = 0;
extensions::api::passwords_private::PasswordCheckState state_ =
extensions::api::passwords_private::PASSWORD_CHECK_STATE_IDLE;
scoped_refptr<password_manager::TestPasswordStore> store_ =
base::MakeRefCounted<password_manager::TestPasswordStore>();
password_manager::SavedPasswordsPresenter presenter_{store_};
password_manager::InsecureCredentialsManager credentials_manager_{&presenter_,
store_};
};
class TestSafetyCheckExtensionService : public TestExtensionService {
public:
void AddExtensionState(const std::string& extension_id,
Enabled enabled,
UserCanDisable user_can_disable) {
state_map_.emplace(extension_id, ExtensionState{enabled.value(),
user_can_disable.value()});
}
bool IsExtensionEnabled(const std::string& extension_id) const override {
auto it = state_map_.find(extension_id);
if (it == state_map_.end()) {
return false;
}
return it->second.enabled;
}
bool UserCanDisableInstalledExtension(
const std::string& extension_id) override {
auto it = state_map_.find(extension_id);
if (it == state_map_.end()) {
return false;
}
return it->second.user_can_disable;
}
private:
struct ExtensionState {
bool enabled;
bool user_can_disable;
};
std::unordered_map<std::string, ExtensionState> state_map_;
};
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
class TestChromeCleanerControllerDelegate
: public safe_browsing::ChromeCleanerControllerDelegate {
public:
bool IsAllowedByPolicy() override { return false; }
};
#endif
} // namespace
class SafetyCheckHandlerTest : public ChromeRenderViewHostTestHarness {
public:
void SetUp() override;
void TearDown() override;
// Returns a |base::DictionaryValue| for safety check status update that
// has the specified |component| and |new_state| if it exists; nullptr
// otherwise.
const base::DictionaryValue* GetSafetyCheckStatusChangedWithDataIfExists(
const std::string& component,
int new_state);
std::string GenerateExtensionId(char char_to_repeat);
void VerifyDisplayString(const base::DictionaryValue* event,
const base::string16& expected);
void VerifyDisplayString(const base::DictionaryValue* event,
const std::string& expected);
// Replaces any instances of browser name (e.g. Google Chrome, Chromium,
// etc) with "browser" to make sure tests work both on Chromium and
// Google Chrome.
void ReplaceBrowserName(base::string16* s);
protected:
safety_check::TestUpdateCheckHelper* update_helper_ = nullptr;
TestVersionUpdater* version_updater_ = nullptr;
std::unique_ptr<password_manager::BulkLeakCheckService> test_leak_service_;
TestPasswordsDelegate test_passwords_delegate_;
extensions::ExtensionPrefs* test_extension_prefs_ = nullptr;
TestSafetyCheckExtensionService test_extension_service_;
content::TestWebUI test_web_ui_;
std::unique_ptr<TestingSafetyCheckHandler> safety_check_;
base::HistogramTester histogram_tester_;
base::test::ScopedFeatureList feature_list_;
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
TestChromeCleanerControllerDelegate test_chrome_cleaner_controller_delegate_;
#endif
};
void SafetyCheckHandlerTest::SetUp() {
ChromeRenderViewHostTestHarness::SetUp();
feature_list_.InitWithFeatures({features::kSafetyCheckWeakPasswords}, {});
// The unique pointer to a TestVersionUpdater gets moved to
// SafetyCheckHandler, but a raw pointer is retained here to change its
// state.
auto update_helper = std::make_unique<safety_check::TestUpdateCheckHelper>();
update_helper_ = update_helper.get();
auto version_updater = std::make_unique<TestVersionUpdater>();
version_updater_ = version_updater.get();
test_leak_service_ = std::make_unique<password_manager::BulkLeakCheckService>(
nullptr, nullptr);
test_passwords_delegate_.SetBulkLeakCheckService(test_leak_service_.get());
test_web_ui_.set_web_contents(web_contents());
test_extension_prefs_ = extensions::ExtensionPrefs::Get(profile());
auto timestamp_delegate = std::make_unique<TestTimestampDelegate>();
safety_check_ = std::make_unique<TestingSafetyCheckHandler>(
std::move(update_helper), std::move(version_updater),
test_leak_service_.get(), &test_passwords_delegate_,
test_extension_prefs_, &test_extension_service_,
std::move(timestamp_delegate));
test_web_ui_.ClearTrackedCalls();
safety_check_->set_web_ui(&test_web_ui_);
safety_check_->AllowJavascript();
}
void SafetyCheckHandlerTest::TearDown() {
test_passwords_delegate_.TearDown();
ChromeRenderViewHostTestHarness::TearDown();
}
const base::DictionaryValue*
SafetyCheckHandlerTest::GetSafetyCheckStatusChangedWithDataIfExists(
const std::string& component,
int new_state) {
// Return the latest update if multiple, so iterate from the end.
const std::vector<std::unique_ptr<content::TestWebUI::CallData>>& call_data =
test_web_ui_.call_data();
for (int i = call_data.size() - 1; i >= 0; --i) {
const content::TestWebUI::CallData& data = *(call_data[i]);
if (data.function_name() != "cr.webUIListenerCallback") {
continue;
}
std::string event;
if ((!data.arg1()->GetAsString(&event)) ||
event != "safety-check-" + component + "-status-changed") {
continue;
}
const base::DictionaryValue* dictionary = nullptr;
if (!data.arg2()->GetAsDictionary(&dictionary)) {
continue;
}
int cur_new_state;
if (dictionary->GetInteger("newState", &cur_new_state) &&
cur_new_state == new_state) {
return dictionary;
}
}
return nullptr;
}
std::string SafetyCheckHandlerTest::GenerateExtensionId(char char_to_repeat) {
return std::string(crx_file::id_util::kIdSize * 2, char_to_repeat);
}
void SafetyCheckHandlerTest::VerifyDisplayString(
const base::DictionaryValue* event,
const base::string16& expected) {
base::string16 display;
ASSERT_TRUE(event->GetString("displayString", &display));
ReplaceBrowserName(&display);
// Need to also replace any instances of Chrome and Chromium in the
// expected string due to an edge case on ChromeOS, where a device name
// is "Chrome", which gets replaced in the display string.
base::string16 expected_replaced = expected;
ReplaceBrowserName(&expected_replaced);
EXPECT_EQ(expected_replaced, display);
}
void SafetyCheckHandlerTest::VerifyDisplayString(
const base::DictionaryValue* event,
const std::string& expected) {
VerifyDisplayString(event, base::ASCIIToUTF16(expected));
}
void SafetyCheckHandlerTest::ReplaceBrowserName(base::string16* s) {
base::ReplaceSubstringsAfterOffset(s, 0, base::ASCIIToUTF16("Google Chrome"),
base::ASCIIToUTF16("Browser"));
base::ReplaceSubstringsAfterOffset(s, 0, base::ASCIIToUTF16("Chrome"),
base::ASCIIToUTF16("Browser"));
base::ReplaceSubstringsAfterOffset(s, 0, base::ASCIIToUTF16("Chromium"),
base::ASCIIToUTF16("Browser"));
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_Checking) {
version_updater_->SetReturnedStatus(VersionUpdater::Status::CHECKING);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kChecking));
ASSERT_TRUE(event);
VerifyDisplayString(event, base::UTF8ToUTF16(""));
// Checking state should not get recorded.
histogram_tester_.ExpectTotalCount("Settings.SafetyCheck.UpdatesResult", 0);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_Updated) {
version_updater_->SetReturnedStatus(VersionUpdater::Status::UPDATED);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kUpdated));
ASSERT_TRUE(event);
#if BUILDFLAG(IS_CHROMEOS_ASH)
base::string16 expected = base::ASCIIToUTF16("Your ") +
ui::GetChromeOSDeviceName() +
base::ASCIIToUTF16(" is up to date");
VerifyDisplayString(event, expected);
#else
VerifyDisplayString(event, "Browser is up to date");
#endif
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kUpdated, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_Updating) {
version_updater_->SetReturnedStatus(VersionUpdater::Status::UPDATING);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kUpdating));
ASSERT_TRUE(event);
#if BUILDFLAG(IS_CHROMEOS_ASH)
VerifyDisplayString(event, "Updating your device");
#else
VerifyDisplayString(event, "Updating Browser");
#endif
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kUpdating, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_Relaunch) {
version_updater_->SetReturnedStatus(VersionUpdater::Status::NEARLY_UPDATED);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kRelaunch));
ASSERT_TRUE(event);
#if BUILDFLAG(IS_CHROMEOS_ASH)
VerifyDisplayString(
event, "Nearly up to date! Restart your device to finish updating.");
#else
VerifyDisplayString(event,
"Nearly up to date! Relaunch Browser to finish "
"updating. Incognito windows won't reopen.");
#endif
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kRelaunch, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_Disabled) {
const char* processor_variation = nullptr;
#if defined(OS_MAC)
switch (base::mac::GetCPUType()) {
case base::mac::CPUType::kIntel:
processor_variation = " (x86_64)";
break;
case base::mac::CPUType::kTranslatedIntel:
processor_variation = " (x86_64 translated)";
break;
case base::mac::CPUType::kArm:
processor_variation = " (arm64)";
break;
}
#elif defined(ARCH_CPU_64_BITS)
processor_variation = " (64-bit)";
#elif defined(ARCH_CPU_32_BITS)
processor_variation = " (32-bit)";
#else
#error Update for a processor that is neither 32-bit nor 64-bit.
#endif // OS_*
version_updater_->SetReturnedStatus(VersionUpdater::Status::DISABLED);
safety_check_->PerformSafetyCheck();
// TODO(crbug/1072432): Since the UNKNOWN state is not present in JS in M83,
// use FAILED_OFFLINE, which uses the same icon.
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kFailedOffline));
ASSERT_TRUE(event);
VerifyDisplayString(
event, "Version " + version_info::GetVersionNumber() + " (" +
(version_info::IsOfficialBuild() ? "Official Build"
: "Developer Build") +
") " + chrome::GetChannelName() + processor_variation);
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kUnknown, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_DisabledByAdmin) {
version_updater_->SetReturnedStatus(
VersionUpdater::Status::DISABLED_BY_ADMIN);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kDisabledByAdmin));
ASSERT_TRUE(event);
VerifyDisplayString(
event,
"Updates are managed by <a target=\"_blank\" "
"href=\"https://support.google.com/chrome?p=your_administrator\">your "
"administrator</a>");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kDisabledByAdmin, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_FailedOffline) {
version_updater_->SetReturnedStatus(VersionUpdater::Status::FAILED_OFFLINE);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kFailedOffline));
ASSERT_TRUE(event);
VerifyDisplayString(event,
"Browser can't check for updates. Try checking your "
"internet connection.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kFailedOffline, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_Failed_ConnectivityOnline) {
update_helper_->SetConnectivity(true);
version_updater_->SetReturnedStatus(VersionUpdater::Status::FAILED);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kFailed));
ASSERT_TRUE(event);
VerifyDisplayString(
event,
"Browser didn't update, something went wrong. <a target=\"_blank\" "
"href=\"https://support.google.com/chrome?p=fix_chrome_updates\">Fix "
"Browser update problems and failed updates.</a>");
histogram_tester_.ExpectBucketCount("Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kFailed,
1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_Failed_ConnectivityOffline) {
update_helper_->SetConnectivity(false);
version_updater_->SetReturnedStatus(VersionUpdater::Status::FAILED);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kFailedOffline));
ASSERT_TRUE(event);
VerifyDisplayString(event,
"Browser can't check for updates. Try checking your "
"internet connection.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.UpdatesResult",
SafetyCheckHandler::UpdateStatus::kFailedOffline, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckUpdates_DestroyedOnJavascriptDisallowed) {
EXPECT_FALSE(TestDestructionVersionUpdater::GetDestructorInvoked());
safety_check_->SetVersionUpdaterForTesting(
std::make_unique<TestDestructionVersionUpdater>());
safety_check_->PerformSafetyCheck();
safety_check_->DisallowJavascript();
EXPECT_TRUE(TestDestructionVersionUpdater::GetDestructorInvoked());
}
TEST_F(SafetyCheckHandlerTest, CheckSafeBrowsing_EnabledStandard) {
TestingProfile::FromWebUI(&test_web_ui_)
->AsTestingProfile()
->GetTestingPrefService()
->SetManagedPref(prefs::kSafeBrowsingEnabled,
std::make_unique<base::Value>(true));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(
SafetyCheckHandler::SafeBrowsingStatus::kEnabledStandard));
ASSERT_TRUE(event);
VerifyDisplayString(event, "Standard Protection is on");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.SafeBrowsingResult",
SafetyCheckHandler::SafeBrowsingStatus::kEnabledStandard, 1);
}
TEST_F(SafetyCheckHandlerTest,
CheckSafeBrowsing_EnabledStandardAvailableEnhanced) {
Profile::FromWebUI(&test_web_ui_)
->GetPrefs()
->SetBoolean(prefs::kSafeBrowsingEnabled, true);
Profile::FromWebUI(&test_web_ui_)
->GetPrefs()
->SetBoolean(prefs::kSafeBrowsingEnhanced, false);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(SafetyCheckHandler::SafeBrowsingStatus::
kEnabledStandardAvailableEnhanced));
ASSERT_TRUE(event);
VerifyDisplayString(event,
"Standard protection is on. For even more security, use "
"enhanced protection.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.SafeBrowsingResult",
SafetyCheckHandler::SafeBrowsingStatus::kEnabledStandardAvailableEnhanced,
1);
}
TEST_F(SafetyCheckHandlerTest, CheckSafeBrowsing_EnabledEnhanced) {
Profile::FromWebUI(&test_web_ui_)
->GetPrefs()
->SetBoolean(prefs::kSafeBrowsingEnabled, true);
Profile::FromWebUI(&test_web_ui_)
->GetPrefs()
->SetBoolean(prefs::kSafeBrowsingEnhanced, true);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(
SafetyCheckHandler::SafeBrowsingStatus::kEnabledEnhanced));
ASSERT_TRUE(event);
VerifyDisplayString(event, "Enhanced Protection is on");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.SafeBrowsingResult",
SafetyCheckHandler::SafeBrowsingStatus::kEnabledEnhanced, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckSafeBrowsing_InconsistentEnhanced) {
// Tests the corner case of SafeBrowsingEnhanced pref being on, while
// SafeBrowsing enabled is off. This should be treated as SB off.
Profile::FromWebUI(&test_web_ui_)
->GetPrefs()
->SetBoolean(prefs::kSafeBrowsingEnabled, false);
Profile::FromWebUI(&test_web_ui_)
->GetPrefs()
->SetBoolean(prefs::kSafeBrowsingEnhanced, true);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(SafetyCheckHandler::SafeBrowsingStatus::kDisabled));
ASSERT_TRUE(event);
VerifyDisplayString(
event, "Safe Browsing is off. Browser recommends turning it on.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.SafeBrowsingResult",
SafetyCheckHandler::SafeBrowsingStatus::kDisabled, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckSafeBrowsing_Disabled) {
Profile::FromWebUI(&test_web_ui_)
->GetPrefs()
->SetBoolean(prefs::kSafeBrowsingEnabled, false);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(SafetyCheckHandler::SafeBrowsingStatus::kDisabled));
ASSERT_TRUE(event);
VerifyDisplayString(
event, "Safe Browsing is off. Browser recommends turning it on.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.SafeBrowsingResult",
SafetyCheckHandler::SafeBrowsingStatus::kDisabled, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckSafeBrowsing_DisabledByAdmin) {
TestingProfile::FromWebUI(&test_web_ui_)
->AsTestingProfile()
->GetTestingPrefService()
->SetManagedPref(prefs::kSafeBrowsingEnabled,
std::make_unique<base::Value>(false));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(
SafetyCheckHandler::SafeBrowsingStatus::kDisabledByAdmin));
ASSERT_TRUE(event);
VerifyDisplayString(
event,
"<a target=\"_blank\" "
"href=\"https://support.google.com/chrome?p=your_administrator\">Your "
"administrator</a> has turned off Safe Browsing");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.SafeBrowsingResult",
SafetyCheckHandler::SafeBrowsingStatus::kDisabledByAdmin, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckSafeBrowsing_DisabledByExtension) {
TestingProfile::FromWebUI(&test_web_ui_)
->AsTestingProfile()
->GetTestingPrefService()
->SetExtensionPref(prefs::kSafeBrowsingEnabled,
std::make_unique<base::Value>(false));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(
SafetyCheckHandler::SafeBrowsingStatus::kDisabledByExtension));
ASSERT_TRUE(event);
VerifyDisplayString(event, "An extension has turned off Safe Browsing");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.SafeBrowsingResult",
SafetyCheckHandler::SafeBrowsingStatus::kDisabledByExtension, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_ObserverRemovedAfterError) {
safety_check_->PerformSafetyCheck();
// First, a "running" change of state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
ASSERT_TRUE(event);
VerifyDisplayString(event, base::UTF8ToUTF16(""));
histogram_tester_.ExpectTotalCount("Settings.SafetyCheck.PasswordsResult", 0);
// Second, an "offline" state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kNetworkError);
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kOffline));
ASSERT_TRUE(event2);
VerifyDisplayString(event2,
"Browser can't check your passwords. Try checking your "
"internet connection.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kOffline, 1);
// Another error, but since the previous state is terminal, the handler
// should no longer be observing the BulkLeakCheckService state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kServiceError);
const base::DictionaryValue* event3 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kOffline));
ASSERT_TRUE(event3);
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kOffline, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_InterruptedAndRefreshed) {
safety_check_->PerformSafetyCheck();
// Password check running.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
ASSERT_TRUE(event);
VerifyDisplayString(event, base::UTF8ToUTF16(""));
// The check gets interrupted and the page is refreshed.
safety_check_->DisallowJavascript();
safety_check_->AllowJavascript();
// Need to set the |TestVersionUpdater| instance again to prevent
// |PerformSafetyCheck()| from creating a real |VersionUpdater| instance.
safety_check_->SetVersionUpdaterForTesting(
std::make_unique<TestVersionUpdater>());
// Another run of the safety check.
safety_check_->PerformSafetyCheck();
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
ASSERT_TRUE(event2);
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kSignedOut);
const base::DictionaryValue* event3 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kSignedOut));
ASSERT_TRUE(event3);
VerifyDisplayString(event3,
"Browser can't check your passwords because you're not "
"signed in");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kSignedOut, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_StartedTwice) {
safety_check_->PerformSafetyCheck();
safety_check_->PerformSafetyCheck();
// First, a "running" change of state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
ASSERT_TRUE(event);
// Then, a network error.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kNetworkError);
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kOffline));
EXPECT_TRUE(event2);
VerifyDisplayString(event2,
"Browser can't check your passwords. Try checking your "
"internet connection.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kOffline, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_ObserverNotifiedTwice) {
safety_check_->PerformSafetyCheck();
EXPECT_TRUE(test_passwords_delegate_.StartPasswordCheckTriggered());
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(
password_manager::BulkLeakCheckService::State::kServiceError);
// Another notification about the same state change.
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(
password_manager::BulkLeakCheckService::State::kServiceError);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kError));
ASSERT_TRUE(event);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_Safe) {
safety_check_->PerformSafetyCheck();
// First, a "running" change of state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
// Second, a "safe" state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kIdle);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kSafe));
EXPECT_TRUE(event);
VerifyDisplayString(event, "No compromised passwords found");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kSafe, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_StaleSafeThenCompromised) {
constexpr int kCompromised = 7;
safety_check_->PerformSafetyCheck();
// First, a "running" change of state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
test_passwords_delegate_.SetPasswordCheckState(
extensions::api::passwords_private::PASSWORD_CHECK_STATE_RUNNING);
EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
// Not a "safe" state, so send an |OnCredentialDone| with is_leaked=true.
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnCredentialDone(
{base::ASCIIToUTF16("login"), base::ASCIIToUTF16("password")},
password_manager::IsLeaked(true));
// The service goes idle, but the disk still has a stale "safe" state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kIdle);
test_passwords_delegate_.SetPasswordCheckState(
extensions::api::passwords_private::PASSWORD_CHECK_STATE_IDLE);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kSafe));
EXPECT_TRUE(event);
// An InsecureCredentialsManager callback fires once the compromised passwords
// get written to disk.
test_passwords_delegate_.SetNumCompromisedCredentials(kCompromised);
test_passwords_delegate_.InvokeOnCompromisedCredentialsChanged();
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kCompromisedExist));
EXPECT_TRUE(event2);
VerifyDisplayString(
event2, base::NumberToString(kCompromised) + " compromised passwords");
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_SafeStateThenMoreEvents) {
safety_check_->PerformSafetyCheck();
// Running state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
test_passwords_delegate_.SetPasswordCheckState(
extensions::api::passwords_private::PASSWORD_CHECK_STATE_RUNNING);
EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
// Previous safe state got loaded.
test_passwords_delegate_.SetNumCompromisedCredentials(0);
test_passwords_delegate_.InvokeOnCompromisedCredentialsChanged();
// The event should get ignored, since the state is still running.
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kSafe));
EXPECT_FALSE(event);
// The check is completed with another safe state.
test_passwords_delegate_.SetPasswordCheckState(
extensions::api::passwords_private::PASSWORD_CHECK_STATE_IDLE);
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kIdle);
// This time the safe state should be reflected.
event = GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords, static_cast<int>(SafetyCheckHandler::PasswordsStatus::kSafe));
EXPECT_TRUE(event);
// After some time, some compromises were discovered (unrelated to SC).
constexpr int kCompromised = 7;
test_passwords_delegate_.SetNumCompromisedCredentials(kCompromised);
test_passwords_delegate_.InvokeOnCompromisedCredentialsChanged();
// The new event should get ignored, since the safe state was final.
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kCompromisedExist));
EXPECT_FALSE(event2);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_OnlyCompromisedExist) {
constexpr int kCompromised = 7;
test_passwords_delegate_.SetNumCompromisedCredentials(kCompromised);
safety_check_->PerformSafetyCheck();
// First, a "running" change of state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
// Compromised passwords found state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kIdle);
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kCompromisedExist));
ASSERT_TRUE(event2);
VerifyDisplayString(
event2, base::NumberToString(kCompromised) + " compromised passwords");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kCompromisedExist, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_CompromisedAndWeakExist) {
constexpr int kCompromised = 7;
constexpr int kWeak = 13;
test_passwords_delegate_.SetNumCompromisedCredentials(kCompromised);
test_passwords_delegate_.SetNumWeakCredentials(kWeak);
safety_check_->PerformSafetyCheck();
// First, a "running" change of state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
// Compromised passwords found state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kIdle);
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kCompromisedExist));
ASSERT_TRUE(event2);
VerifyDisplayString(
event2, base::NumberToString(kCompromised) + " compromised passwords, " +
base::NumberToString(kWeak) + " weak passwords");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kCompromisedExist, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_OnlyWeakExist) {
constexpr int kWeak = 13;
test_passwords_delegate_.SetNumWeakCredentials(kWeak);
safety_check_->PerformSafetyCheck();
// First, a "running" change of state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kRunning);
EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking)));
// Compromised passwords found state.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kIdle);
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kWeakPasswordsExist));
ASSERT_TRUE(event2);
VerifyDisplayString(event2, base::NumberToString(kWeak) + " weak passwords");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kWeakPasswordsExist, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_Error) {
safety_check_->PerformSafetyCheck();
EXPECT_TRUE(test_passwords_delegate_.StartPasswordCheckTriggered());
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(
password_manager::BulkLeakCheckService::State::kServiceError);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kError));
ASSERT_TRUE(event);
VerifyDisplayString(event,
"Browser can't check your passwords. Try again "
"later.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kError, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_Error_FutureEventsIgnored) {
safety_check_->PerformSafetyCheck();
EXPECT_TRUE(test_passwords_delegate_.StartPasswordCheckTriggered());
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(
password_manager::BulkLeakCheckService::State::kServiceError);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kError));
ASSERT_TRUE(event);
VerifyDisplayString(event,
"Browser can't check your passwords. Try again "
"later.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kError, 1);
// At some point later, the service discovers compromised passwords and goes
// idle.
constexpr int kCompromised = 7;
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(password_manager::BulkLeakCheckService::State::kRunning);
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(password_manager::BulkLeakCheckService::State::kIdle);
// An InsecureCredentialsManager callback fires once the compromised passwords
// get written to disk.
test_passwords_delegate_.SetNumCompromisedCredentials(kCompromised);
test_passwords_delegate_.InvokeOnCompromisedCredentialsChanged();
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kCompromisedExist));
// The event for compromised passwords should not exist, since the changes
// should no longer be observed.
EXPECT_FALSE(event2);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_FeatureUnavailable) {
safety_check_->PerformSafetyCheck();
EXPECT_TRUE(test_passwords_delegate_.StartPasswordCheckTriggered());
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(
password_manager::BulkLeakCheckService::State::kTokenRequestFailure);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kFeatureUnavailable));
ASSERT_TRUE(event);
VerifyDisplayString(event, "Password check is not available in Chromium");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kFeatureUnavailable, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_RunningOneCompromised) {
test_passwords_delegate_.SetNumCompromisedCredentials(1);
safety_check_->PerformSafetyCheck();
EXPECT_TRUE(test_passwords_delegate_.StartPasswordCheckTriggered());
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnStateChanged(password_manager::BulkLeakCheckService::State::kIdle);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(
SafetyCheckHandler::PasswordsStatus::kCompromisedExist));
ASSERT_TRUE(event);
VerifyDisplayString(event, "1 compromised password");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kCompromisedExist, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_NoPasswords) {
test_passwords_delegate_.ClearSavedPasswordsList();
test_passwords_delegate_.SetStartPasswordCheckState(
password_manager::BulkLeakCheckService::State::kIdle);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kNoPasswords));
EXPECT_TRUE(event);
VerifyDisplayString(event,
"No saved passwords. Chrome can check your passwords "
"when you save them.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.PasswordsResult",
SafetyCheckHandler::PasswordsStatus::kNoPasswords, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckPasswords_Progress) {
auto credential = password_manager::LeakCheckCredential(
base::UTF8ToUTF16("test"), base::UTF8ToUTF16("test"));
auto is_leaked = password_manager::IsLeaked(false);
safety_check_->PerformSafetyCheck();
test_passwords_delegate_.SetPasswordCheckState(
extensions::api::passwords_private::PASSWORD_CHECK_STATE_RUNNING);
test_passwords_delegate_.SetProgress(1, 3);
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnCredentialDone(credential, is_leaked);
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
EXPECT_TRUE(event);
VerifyDisplayString(event, base::UTF8ToUTF16("Checking passwords (1 of 3)…"));
test_passwords_delegate_.SetProgress(2, 3);
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnCredentialDone(credential, is_leaked);
const base::DictionaryValue* event2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
EXPECT_TRUE(event2);
VerifyDisplayString(event2,
base::UTF8ToUTF16("Checking passwords (2 of 3)…"));
// Final update comes after status change, so no new progress message should
// be present.
test_passwords_delegate_.SetPasswordCheckState(
extensions::api::passwords_private::PASSWORD_CHECK_STATE_IDLE);
test_passwords_delegate_.SetProgress(3, 3);
static_cast<password_manager::BulkLeakCheckService::Observer*>(
safety_check_.get())
->OnCredentialDone(credential, is_leaked);
const base::DictionaryValue* event3 =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
EXPECT_TRUE(event3);
// Still 2/3 event.
VerifyDisplayString(event3,
base::UTF8ToUTF16("Checking passwords (2 of 3)…"));
}
TEST_F(SafetyCheckHandlerTest, CheckExtensions_NoExtensions) {
safety_check_->PerformSafetyCheck();
EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions,
static_cast<int>(
SafetyCheckHandler::ExtensionsStatus::kNoneBlocklisted)));
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ExtensionsResult",
SafetyCheckHandler::ExtensionsStatus::kNoneBlocklisted, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckExtensions_NoneBlocklisted) {
std::string extension_id = GenerateExtensionId('a');
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder(extension_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension.get(), extensions::Extension::State::ENABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension_id, extensions::NOT_BLOCKLISTED);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions,
static_cast<int>(
SafetyCheckHandler::ExtensionsStatus::kNoneBlocklisted));
EXPECT_TRUE(event);
VerifyDisplayString(event,
"You're protected from potentially harmful extensions");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ExtensionsResult",
SafetyCheckHandler::ExtensionsStatus::kNoneBlocklisted, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckExtensions_BlocklistedAllDisabled) {
std::string extension_id = GenerateExtensionId('a');
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test0").SetID(extension_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension.get(), extensions::Extension::State::DISABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension_id, extensions::BLOCKLISTED_MALWARE);
test_extension_service_.AddExtensionState(extension_id, Enabled(false),
UserCanDisable(false));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions,
static_cast<int>(
SafetyCheckHandler::ExtensionsStatus::kBlocklistedAllDisabled));
EXPECT_TRUE(event);
VerifyDisplayString(
event, "1 potentially harmful extension is off. You can also remove it.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ExtensionsResult",
SafetyCheckHandler::ExtensionsStatus::kBlocklistedAllDisabled, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckExtensions_BlocklistedReenabledAllByUser) {
std::string extension_id = GenerateExtensionId('a');
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test0").SetID(extension_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension.get(), extensions::Extension::State::ENABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension_id, extensions::BLOCKLISTED_POTENTIALLY_UNWANTED);
test_extension_service_.AddExtensionState(extension_id, Enabled(true),
UserCanDisable(true));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions, static_cast<int>(SafetyCheckHandler::ExtensionsStatus::
kBlocklistedReenabledAllByUser));
EXPECT_TRUE(event);
VerifyDisplayString(event,
"You turned 1 potentially harmful extension back on");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ExtensionsResult",
SafetyCheckHandler::ExtensionsStatus::kBlocklistedReenabledAllByUser, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckExtensions_BlocklistedReenabledAllByAdmin) {
std::string extension_id = GenerateExtensionId('a');
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test0").SetID(extension_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension.get(), extensions::Extension::State::ENABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension_id, extensions::BLOCKLISTED_POTENTIALLY_UNWANTED);
test_extension_service_.AddExtensionState(extension_id, Enabled(true),
UserCanDisable(false));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions, static_cast<int>(SafetyCheckHandler::ExtensionsStatus::
kBlocklistedReenabledAllByAdmin));
VerifyDisplayString(event,
"Your administrator turned 1 potentially harmful "
"extension back on");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ExtensionsResult",
SafetyCheckHandler::ExtensionsStatus::kBlocklistedReenabledAllByAdmin, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckExtensions_BlocklistedReenabledSomeByUser) {
std::string extension_id = GenerateExtensionId('a');
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test0").SetID(extension_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension.get(), extensions::Extension::State::ENABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension_id, extensions::BLOCKLISTED_POTENTIALLY_UNWANTED);
test_extension_service_.AddExtensionState(extension_id, Enabled(true),
UserCanDisable(true));
std::string extension2_id = GenerateExtensionId('b');
scoped_refptr<const extensions::Extension> extension2 =
extensions::ExtensionBuilder("test1").SetID(extension2_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension2.get(), extensions::Extension::State::ENABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension2_id, extensions::BLOCKLISTED_POTENTIALLY_UNWANTED);
test_extension_service_.AddExtensionState(extension2_id, Enabled(true),
UserCanDisable(false));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions, static_cast<int>(SafetyCheckHandler::ExtensionsStatus::
kBlocklistedReenabledSomeByUser));
EXPECT_TRUE(event);
VerifyDisplayString(event,
"You turned 1 potentially harmful extension back "
"on. Your administrator "
"turned 1 potentially harmful extension back on.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ExtensionsResult",
SafetyCheckHandler::ExtensionsStatus::kBlocklistedReenabledSomeByUser, 1);
}
TEST_F(SafetyCheckHandlerTest, CheckExtensions_Error) {
// One extension in the error state.
std::string extension_id = GenerateExtensionId('a');
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder("test0").SetID(extension_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension.get(), extensions::Extension::State::ENABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension_id, extensions::BLOCKLISTED_UNKNOWN);
test_extension_service_.AddExtensionState(extension_id, Enabled(true),
UserCanDisable(true));
// Another extension blocklisted.
std::string extension2_id = GenerateExtensionId('b');
scoped_refptr<const extensions::Extension> extension2 =
extensions::ExtensionBuilder("test1").SetID(extension2_id).Build();
test_extension_prefs_->OnExtensionInstalled(
extension2.get(), extensions::Extension::State::ENABLED,
syncer::StringOrdinal(), "");
test_extension_prefs_->SetExtensionBlocklistState(
extension2_id, extensions::BLOCKLISTED_POTENTIALLY_UNWANTED);
test_extension_service_.AddExtensionState(extension2_id, Enabled(true),
UserCanDisable(false));
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions,
static_cast<int>(SafetyCheckHandler::ExtensionsStatus::kError));
EXPECT_TRUE(event);
VerifyDisplayString(event,
"Browser can't check your extensions. Try again later.");
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ExtensionsResult",
SafetyCheckHandler::ExtensionsStatus::kError, 1);
}
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
class SafetyCheckHandlerChromeCleanerIdleTest
: public SafetyCheckHandlerTest,
public testing::WithParamInterface<
std::tuple<safe_browsing::ChromeCleanerController::IdleReason,
SafetyCheckHandler::ChromeCleanerStatus,
base::string16>> {
protected:
void SetUp() override {
SafetyCheckHandlerTest::SetUp();
idle_reason_ = testing::get<0>(GetParam());
expected_cct_status_ = testing::get<1>(GetParam());
expected_display_string_ = testing::get<2>(GetParam());
}
safe_browsing::ChromeCleanerController::IdleReason idle_reason_;
SafetyCheckHandler::ChromeCleanerStatus expected_cct_status_;
base::string16 expected_display_string_;
};
TEST_P(SafetyCheckHandlerChromeCleanerIdleTest, CheckChromeCleanerIdleStates) {
safe_browsing::ChromeCleanerControllerImpl::ResetInstanceForTesting();
safe_browsing::ChromeCleanerControllerImpl::GetInstance()->SetIdleForTesting(
idle_reason_);
safety_check_->PerformSafetyCheck();
// Ensure WebUI event is sent.
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kChromeCleaner, static_cast<int>(expected_cct_status_));
ASSERT_TRUE(event);
VerifyDisplayString(event, expected_display_string_);
// Ensure UMA is logged.
if (expected_cct_status_ ==
SafetyCheckHandler::ChromeCleanerStatus::kHidden ||
expected_cct_status_ ==
SafetyCheckHandler::ChromeCleanerStatus::kChecking) {
// Hidden and checking state should not get recorded.
histogram_tester_.ExpectTotalCount(
"Settings.SafetyCheck.ChromeCleanerResult", 0);
} else {
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ChromeCleanerResult", expected_cct_status_, 1);
}
}
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_Initial,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::kInitial,
SafetyCheckHandler::ChromeCleanerStatus::kNoUwsFoundWithTimestamp,
base::UTF8ToUTF16("Browser didn't find harmful software on your "
"computer • Checked just now"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_ReporterFoundNothing,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::
kReporterFoundNothing,
SafetyCheckHandler::ChromeCleanerStatus::kNoUwsFoundWithTimestamp,
base::UTF8ToUTF16("Browser didn't find harmful software on your "
"computer • Checked just now"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_ReporterFailed,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::kReporterFailed,
SafetyCheckHandler::ChromeCleanerStatus::kError,
base::UTF8ToUTF16("Something went wrong. Click for more details."))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_ScanningFoundNothing,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::
kScanningFoundNothing,
SafetyCheckHandler::ChromeCleanerStatus::kNoUwsFoundWithTimestamp,
base::UTF8ToUTF16("Browser didn't find harmful software on your "
"computer • Checked just now"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_ScanningFailed,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::kScanningFailed,
SafetyCheckHandler::ChromeCleanerStatus::kError,
base::UTF8ToUTF16("Something went wrong. Click for more details."))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_ConnectionLost,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::kConnectionLost,
SafetyCheckHandler::ChromeCleanerStatus::kInfected,
base::UTF8ToUTF16("Browser found harmful software on your computer"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_UserDeclinedCleanup,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::
kUserDeclinedCleanup,
SafetyCheckHandler::ChromeCleanerStatus::kInfected,
base::UTF8ToUTF16("Browser found harmful software on your computer"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_CleaningFailed,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::kCleaningFailed,
SafetyCheckHandler::ChromeCleanerStatus::kError,
base::UTF8ToUTF16("Something went wrong. Click for more details."))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_CleaningSucceed,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::kCleaningSucceeded,
SafetyCheckHandler::ChromeCleanerStatus::kNoUwsFoundWithTimestamp,
base::UTF8ToUTF16("Browser didn't find harmful software on your "
"computer • Checked just now"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_CleanerDownloadFailed,
SafetyCheckHandlerChromeCleanerIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::IdleReason::
kCleanerDownloadFailed,
SafetyCheckHandler::ChromeCleanerStatus::kError,
base::UTF8ToUTF16("Something went wrong. Click for more details."))));
class SafetyCheckHandlerChromeCleanerNonIdleTest
: public SafetyCheckHandlerTest,
public testing::WithParamInterface<
std::tuple<safe_browsing::ChromeCleanerController::State,
SafetyCheckHandler::ChromeCleanerStatus,
base::string16>> {
protected:
void SetUp() override {
SafetyCheckHandlerTest::SetUp();
state_ = testing::get<0>(GetParam());
expected_cct_status_ = testing::get<1>(GetParam());
expected_display_string_ = testing::get<2>(GetParam());
}
safe_browsing::ChromeCleanerController::State state_;
SafetyCheckHandler::ChromeCleanerStatus expected_cct_status_;
base::string16 expected_display_string_;
};
TEST_P(SafetyCheckHandlerChromeCleanerNonIdleTest,
CheckChromeCleanerNonIdleStates) {
safe_browsing::ChromeCleanerControllerImpl::ResetInstanceForTesting();
safe_browsing::ChromeCleanerControllerImpl::GetInstance()->SetStateForTesting(
state_);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kChromeCleaner, static_cast<int>(expected_cct_status_));
ASSERT_TRUE(event);
VerifyDisplayString(event, expected_display_string_);
// Ensure UMA is logged.
if (expected_cct_status_ ==
SafetyCheckHandler::ChromeCleanerStatus::kHidden ||
expected_cct_status_ ==
SafetyCheckHandler::ChromeCleanerStatus::kChecking) {
// Hidden and checking state should not get recorded.
histogram_tester_.ExpectTotalCount(
"Settings.SafetyCheck.ChromeCleanerResult", 0);
} else {
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ChromeCleanerResult", expected_cct_status_, 1);
}
}
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_ReporterRunning,
SafetyCheckHandlerChromeCleanerNonIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::State::kReporterRunning,
SafetyCheckHandler::ChromeCleanerStatus::kScanningForUws,
base::UTF8ToUTF16(
"Browser is checking your computer for harmful software…"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_Scanning,
SafetyCheckHandlerChromeCleanerNonIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::State::kScanning,
SafetyCheckHandler::ChromeCleanerStatus::kScanningForUws,
base::UTF8ToUTF16(
"Browser is checking your computer for harmful software…"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_Cleaning,
SafetyCheckHandlerChromeCleanerNonIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::State::kCleaning,
SafetyCheckHandler::ChromeCleanerStatus::kRemovingUws,
base::UTF8ToUTF16(
"Browser is removing harmful software from your computer…"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_Infected,
SafetyCheckHandlerChromeCleanerNonIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::State::kInfected,
SafetyCheckHandler::ChromeCleanerStatus::kInfected,
base::UTF8ToUTF16("Browser found harmful software on your computer"))));
INSTANTIATE_TEST_SUITE_P(
CheckChromeCleaner_RebootRequired,
SafetyCheckHandlerChromeCleanerNonIdleTest,
::testing::Values(std::make_tuple(
safe_browsing::ChromeCleanerController::State::kRebootRequired,
SafetyCheckHandler::ChromeCleanerStatus::kRebootRequired,
base::UTF8ToUTF16(
"To finish removing harmful software, restart your computer"))));
TEST_F(SafetyCheckHandlerTest, CheckChromeCleaner_DisabledByAdmin) {
safe_browsing::ChromeCleanerControllerImpl::ResetInstanceForTesting();
safe_browsing::ChromeCleanerControllerImpl::GetInstance()
->SetDelegateForTesting(&test_chrome_cleaner_controller_delegate_);
safety_check_->PerformSafetyCheck();
const base::DictionaryValue* event =
GetSafetyCheckStatusChangedWithDataIfExists(
kChromeCleaner,
static_cast<int>(
SafetyCheckHandler::ChromeCleanerStatus::kDisabledByAdmin));
ASSERT_TRUE(event);
VerifyDisplayString(
event,
"<a target=\"_blank\" "
"href=\"https://support.google.com/chrome?p=your_administrator\">Your "
"administrator</a> has turned off checking for harmful software");
}
TEST_F(SafetyCheckHandlerTest, CheckChromeCleaner_ObserverUpdateLogging) {
safe_browsing::ChromeCleanerControllerImpl::ResetInstanceForTesting();
safe_browsing::ChromeCleanerControllerImpl::GetInstance()->SetIdleForTesting(
safe_browsing::ChromeCleanerController::IdleReason::
kReporterFoundNothing);
// We expect a user triggering a safety check to log the Chrome cleaner
// result.
safety_check_->PerformSafetyCheck();
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ChromeCleanerResult",
SafetyCheckHandler::ChromeCleanerStatus::kNoUwsFoundWithTimestamp, 1);
// Subsequent Chrome cleaner status updates without the user running safety
// check again should not trigger logging.
safety_check_->OnIdle(safe_browsing::ChromeCleanerController::IdleReason::
kReporterFoundNothing);
safety_check_->OnReporterRunning();
safety_check_->OnScanning();
safety_check_->OnRebootRequired();
safety_check_->OnRebootFailed();
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ChromeCleanerResult",
SafetyCheckHandler::ChromeCleanerStatus::kNoUwsFoundWithTimestamp, 1);
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ChromeCleanerResult",
SafetyCheckHandler::ChromeCleanerStatus::kRebootRequired, 0);
histogram_tester_.ExpectBucketCount(
"Settings.SafetyCheck.ChromeCleanerResult",
SafetyCheckHandler::ChromeCleanerStatus::kScanningForUws, 0);
}
#endif
TEST_F(SafetyCheckHandlerTest, CheckParentRanDisplayString) {
// 1 second before midnight Dec 31st 2020, so that -(24h-1s) is still on the
// same day. This test time is hard coded to prevent DST flakiness, see
// crbug.com/1066576.
const base::Time system_time =
base::Time::FromDoubleT(1609459199).LocalMidnight() -
base::TimeDelta::FromSeconds(1);
// Display strings for given time deltas in seconds.
std::vector<std::tuple<std::string, int>> tuples{
std::make_tuple("a moment ago", 1),
std::make_tuple("a moment ago", 59),
std::make_tuple("1 minute ago", 60),
std::make_tuple("2 minutes ago", 60 * 2),
std::make_tuple("59 minutes ago", 60 * 60 - 1),
std::make_tuple("1 hour ago", 60 * 60),
std::make_tuple("2 hours ago", 60 * 60 * 2),
std::make_tuple("23 hours ago", 60 * 60 * 23),
std::make_tuple("yesterday", 60 * 60 * 24),
std::make_tuple("yesterday", 60 * 60 * 24 * 2 - 1),
std::make_tuple("2 days ago", 60 * 60 * 24 * 2),
std::make_tuple("2 days ago", 60 * 60 * 24 * 3 - 1),
std::make_tuple("3 days ago", 60 * 60 * 24 * 3),
std::make_tuple("3 days ago", 60 * 60 * 24 * 4 - 1)};
// Test that above time deltas produce the corresponding display strings.
for (auto tuple : tuples) {
const base::Time time =
system_time - base::TimeDelta::FromSeconds(std::get<1>(tuple));
const base::string16 display_string =
safety_check_->GetStringForParentRan(time, system_time);
EXPECT_EQ(base::UTF8ToUTF16(
base::StrCat({"Safety check ran ", std::get<0>(tuple)})),
display_string);
}
}
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
TEST_F(SafetyCheckHandlerTest, CheckChromeCleanerRanDisplayString) {
// Test string without timestamp.
base::Time null_time;
base::string16 display_string =
safety_check_->GetStringForChromeCleanerRan(null_time, null_time);
ReplaceBrowserName(&display_string);
EXPECT_EQ(display_string,
base::UTF8ToUTF16(
"Browser can check your computer for harmful software"));
// Test strings with timestamp.
// 1 second before midnight Dec 31st 2020, so that -(24h-1s) is still on the
// same day. This test time is hard coded to prevent DST flakiness, see
// crbug.com/1066576.
const base::Time system_time =
base::Time::FromDoubleT(1609459199).LocalMidnight() -
base::TimeDelta::FromSeconds(1);
// Display strings for given time deltas in seconds.
std::vector<std::tuple<std::string, int>> tuples{
std::make_tuple("just now", 1),
std::make_tuple("just now", 59),
std::make_tuple("1 minute ago", 60),
std::make_tuple("2 minutes ago", 60 * 2),
std::make_tuple("59 minutes ago", 60 * 60 - 1),
std::make_tuple("1 hour ago", 60 * 60),
std::make_tuple("2 hours ago", 60 * 60 * 2),
std::make_tuple("23 hours ago", 60 * 60 * 23),
std::make_tuple("yesterday", 60 * 60 * 24),
std::make_tuple("yesterday", 60 * 60 * 24 * 2 - 1),
std::make_tuple("2 days ago", 60 * 60 * 24 * 2),
std::make_tuple("2 days ago", 60 * 60 * 24 * 3 - 1),
std::make_tuple("3 days ago", 60 * 60 * 24 * 3),
std::make_tuple("3 days ago", 60 * 60 * 24 * 4 - 1)};
// Test that above time deltas produce the corresponding display strings.
for (auto tuple : tuples) {
const base::Time time =
system_time - base::TimeDelta::FromSeconds(std::get<1>(tuple));
display_string =
safety_check_->GetStringForChromeCleanerRan(time, system_time);
ReplaceBrowserName(&display_string);
EXPECT_EQ(base::UTF8ToUTF16(
base::StrCat({"Browser didn't find harmful software on "
"your computer • Checked ",
std::get<0>(tuple)})),
display_string);
}
}
#endif
TEST_F(SafetyCheckHandlerTest, CheckSafetyCheckStartedWebUiEvents) {
safety_check_->SendSafetyCheckStartedWebUiUpdates();
// Check that all initial updates ("running" states) are sent.
const base::DictionaryValue* event_parent =
GetSafetyCheckStatusChangedWithDataIfExists(
kParent,
static_cast<int>(SafetyCheckHandler::ParentStatus::kChecking));
ASSERT_TRUE(event_parent);
VerifyDisplayString(event_parent, base::UTF8ToUTF16("Running…"));
const base::DictionaryValue* event_updates =
GetSafetyCheckStatusChangedWithDataIfExists(
kUpdates,
static_cast<int>(SafetyCheckHandler::UpdateStatus::kChecking));
ASSERT_TRUE(event_updates);
VerifyDisplayString(event_updates, base::UTF8ToUTF16(""));
const base::DictionaryValue* event_pws =
GetSafetyCheckStatusChangedWithDataIfExists(
kPasswords,
static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
ASSERT_TRUE(event_pws);
VerifyDisplayString(event_pws, base::UTF8ToUTF16(""));
const base::DictionaryValue* event_sb =
GetSafetyCheckStatusChangedWithDataIfExists(
kSafeBrowsing,
static_cast<int>(SafetyCheckHandler::SafeBrowsingStatus::kChecking));
ASSERT_TRUE(event_sb);
VerifyDisplayString(event_sb, base::UTF8ToUTF16(""));
const base::DictionaryValue* event_extensions =
GetSafetyCheckStatusChangedWithDataIfExists(
kExtensions,
static_cast<int>(SafetyCheckHandler::ExtensionsStatus::kChecking));
ASSERT_TRUE(event_extensions);
VerifyDisplayString(event_extensions, base::UTF8ToUTF16(""));
}
TEST_F(SafetyCheckHandlerTest, CheckSafetyCheckCompletedWebUiEvents) {
// Mock safety check invocation.
safety_check_->PerformSafetyCheck();
// Set the password check mock response.
test_leak_service_->set_state_and_notify(
password_manager::BulkLeakCheckService::State::kSignedOut);
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Set the Chrome cleaner mock response.
safe_browsing::ChromeCleanerControllerImpl::ResetInstanceForTesting();
safe_browsing::ChromeCleanerControllerImpl::GetInstance()->SetStateForTesting(
safe_browsing::ChromeCleanerController::State::kInfected);
#endif
// Check that the parent update is sent after all children checks completed.
const base::DictionaryValue* event_parent =
GetSafetyCheckStatusChangedWithDataIfExists(
kParent, static_cast<int>(SafetyCheckHandler::ParentStatus::kAfter));
ASSERT_TRUE(event_parent);
VerifyDisplayString(event_parent,
base::UTF8ToUTF16("Safety check ran a moment ago"));
#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Subsequent Chrome cleaner status updates without the user running safety
// check again should not trigger further parent element completion events.
safety_check_->OnIdle(safe_browsing::ChromeCleanerController::IdleReason::
kReporterFoundNothing);
safety_check_->OnReporterRunning();
safety_check_->OnScanning();
safety_check_->OnRebootRequired();
safety_check_->OnRebootFailed();
#endif
// Check that there is no new parent completion event.
const base::DictionaryValue* event_parent2 =
GetSafetyCheckStatusChangedWithDataIfExists(
kParent, static_cast<int>(SafetyCheckHandler::ParentStatus::kAfter));
ASSERT_TRUE(event_parent2);
ASSERT_TRUE(event_parent == event_parent2);
}