blob: e09c880d80f6995e87991bad5b26d43da93e64e8 [file] [log] [blame]
// Copyright 2022 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/ui/webui/settings/safety_hub_handler.h"
#include <ctime>
#include <memory>
#include "base/containers/fixed_flat_set.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gtest_util.h"
#include "base/test/simple_test_clock.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/extensions/cws_info_service.h"
#include "chrome/browser/extensions/cws_info_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/password_manager/password_manager_test_util.h"
#include "chrome/browser/permissions/notifications_engagement_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/safety_hub/notification_permission_review_service_factory.h"
#include "chrome/browser/ui/safety_hub/password_status_check_service.h"
#include "chrome/browser/ui/safety_hub/password_status_check_service_factory.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_test_util.h"
#include "chrome/browser/ui/safety_hub/unused_site_permissions_service.h"
#include "chrome/browser/ui/webui/settings/site_settings_helper.h"
#include "chrome/browser/ui/webui/version/version_ui.h"
#include "chrome/browser/upgrade_detector/build_state.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_version.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/features.h"
#include "components/crx_file/id_util.h"
#include "components/password_manager/core/browser/password_store/test_password_store.h"
#include "components/permissions/constants.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_web_ui.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using extensions::mojom::ManifestLocation;
using password_manager::TestPasswordStore;
using safety_hub::SafetyHubCardState;
enum SettingManager { USER, ADMIN, EXTENSION };
constexpr char kUnusedTestSite[] = "https://example1.com";
constexpr char kUsedTestSite[] = "https://example2.com";
constexpr char16_t kUsername[] = u"bob";
constexpr char16_t kCompromisedPassword[] = u"fnlsr4@cm^mdls@fkspnsg3d";
constexpr ContentSettingsType kUnusedRegularPermission =
ContentSettingsType::GEOLOCATION;
constexpr ContentSettingsType kUnusedChooserPermission =
ContentSettingsType::FILE_SYSTEM_ACCESS_CHOOSER_DATA;
class SafetyHubHandlerTest : public testing::Test {
public:
SafetyHubHandlerTest() {
feature_list_.InitWithFeatures(
/*enabled_features=*/
{content_settings::features::kSafetyCheckUnusedSitePermissions,
content_settings::features::
kSafetyCheckUnusedSitePermissionsForSupportedChooserPermissions,
features::kSafetyHubExtensionsUwSTrigger,
features::kSafetyHubExtensionsOffStoreTrigger,
features::kSafetyHub},
/*disabled_features=*/{});
}
void SetUp() override {
// Set clock for HostContentSettingsMap.
base::Time time;
ASSERT_TRUE(base::Time::FromString("2022-09-07 13:00", &time));
clock_.SetNow(time);
hcsm_ = HostContentSettingsMapFactory::GetForProfile(profile());
hcsm_->SetClockForTesting(&clock_);
handler_ = std::make_unique<SafetyHubHandler>(profile());
handler()->set_web_ui(web_ui());
handler()->AllowJavascript();
// Create a revoked permission.
AddRevokedPermission();
// There should be only an unused URL in the revoked permissions list.
const auto& revoked_permissions =
handler()->PopulateUnusedSitePermissionsData();
EXPECT_EQ(revoked_permissions.size(), 1UL);
EXPECT_EQ(GURL(kUnusedTestSite),
GURL(*revoked_permissions[0].GetDict().FindString(
site_settings::kOrigin)));
// Run password check to fetch latest result from disk.
safety_hub_test_util::UpdatePasswordCheckServiceAsync(
PasswordStatusCheckServiceFactory::GetForProfile(profile()));
}
void TearDown() override {
auto* partition = profile()->GetDefaultStoragePartition();
if (partition) {
partition->WaitForDeletionTasksForTesting();
}
}
void AddNotificationPermissionsForReview() {
// Set a host to have large number of notifications and keep engagement as
// NONE.
GURL url = GURL("https://example0.org:443");
hcsm()->SetContentSettingDefaultScope(
url, GURL(), ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
auto* notifications_engagement_service =
NotificationsEngagementServiceFactory::GetForProfile(profile());
notifications_engagement_service->RecordNotificationDisplayed(url, 35);
// Trigger the update for changes to be seen.
NotificationPermissionsReviewService* service =
NotificationPermissionsReviewServiceFactory::GetForProfile(profile());
CHECK(service);
service->UpdateAsync();
RunUntilIdle();
}
void AddRevokedPermission() {
auto dict =
base::Value::Dict()
.Set(permissions::kRevokedKey,
base::Value::List()
.Append(static_cast<int32_t>(kUnusedRegularPermission))
.Append(static_cast<int32_t>(kUnusedChooserPermission)))
.Set(permissions::kRevokedChooserPermissionsKey,
base::Value::Dict().Set(
base::NumberToString(
static_cast<int32_t>(kUnusedChooserPermission)),
base::Value::Dict().Set("foo", "bar")));
hcsm()->SetWebsiteSettingDefaultScope(
GURL(kUnusedTestSite), GURL(kUnusedTestSite),
ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
base::Value(dict.Clone()));
}
void CreateLeakedCredential() {
profile_store().AddLogin(
MakeForm(kUsername, kCompromisedPassword, kUsedTestSite, true));
PasswordStatusCheckService* password_service =
PasswordStatusCheckServiceFactory::GetForProfile(profile());
safety_hub_test_util::UpdatePasswordCheckServiceAsync(password_service);
EXPECT_EQ(password_service->compromised_credential_count(), 1UL);
}
void FixLeakedCredential() {
profile_store().UpdateLogin(
MakeForm(kUsername, u"new_fnlsr4@cm^mls@fkspnsg3d"));
PasswordStatusCheckService* password_service =
PasswordStatusCheckServiceFactory::GetForProfile(profile());
safety_hub_test_util::UpdatePasswordCheckServiceAsync(password_service);
EXPECT_EQ(password_service->compromised_credential_count(), 0UL);
}
void AddExtensionsForReview() {
extensions::CWSInfoServiceFactory::GetInstance()->SetTestingFactory(
profile(), base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return safety_hub_test_util::GetMockCWSInfoService(
Profile::FromBrowserContext(context));
}));
safety_hub_test_util::CreateMockExtensions(profile());
}
void AddTriggeringExtension() {
handler_->SetTriggeringExtensionForTesting("Test");
}
void ExpectRevokedPermission() {
ContentSettingsForOneType revoked_permissions_list =
hcsm()->GetSettingsForOneType(
ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS);
EXPECT_EQ(1U, revoked_permissions_list.size());
EXPECT_EQ(
ContentSetting::CONTENT_SETTING_ASK,
hcsm()->GetContentSetting(GURL(kUnusedTestSite), GURL(kUnusedTestSite),
kUnusedRegularPermission));
EXPECT_EQ(base::Value(), hcsm()->GetWebsiteSetting(
GURL(kUnusedTestSite), GURL(kUnusedTestSite),
kUnusedChooserPermission));
}
void ValidateNotificationPermissionUpdate() {
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
EXPECT_EQ("cr.webUIListenerCallback", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ("notification-permission-review-list-maybe-changed",
data.arg1()->GetString());
ASSERT_TRUE(data.arg2()->is_list());
}
void SetPrefsForSafeBrowsing(bool is_enabled,
bool is_enhanced,
SettingManager managed_by) {
auto* prefs = profile()->GetTestingPrefService();
switch (managed_by) {
case USER:
prefs->SetUserPref(prefs::kSafeBrowsingEnabled,
std::make_unique<base::Value>(is_enabled));
prefs->SetUserPref(prefs::kSafeBrowsingEnhanced,
std::make_unique<base::Value>(is_enhanced));
break;
case ADMIN:
prefs->SetManagedPref(prefs::kSafeBrowsingEnabled,
std::make_unique<base::Value>(is_enabled));
prefs->SetManagedPref(prefs::kSafeBrowsingEnhanced,
std::make_unique<base::Value>(is_enhanced));
break;
case EXTENSION:
prefs->SetExtensionPref(prefs::kSafeBrowsingEnabled,
std::make_unique<base::Value>(is_enabled));
prefs->SetExtensionPref(prefs::kSafeBrowsingEnhanced,
std::make_unique<base::Value>(is_enhanced));
break;
default:
NOTREACHED() << "Unexpected value for managed_by argument. \n";
}
}
void ValidateHandleSafeBrowsingCardData(std::string header,
std::string subheader,
SafetyHubCardState state) {
base::Value::List args;
args.Append("getSafeBrowsingState");
handler()->HandleGetSafeBrowsingCardData(args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
EXPECT_EQ("cr.webUIResponse", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ("getSafeBrowsingState", data.arg1()->GetString());
// arg2 is a boolean that is true if the callback is successful.
ASSERT_TRUE(data.arg2()->is_bool());
ASSERT_TRUE(data.arg2());
ASSERT_TRUE(data.arg3()->is_dict());
EXPECT_EQ(header, *data.arg3()->GetDict().FindString("header"));
EXPECT_EQ(subheader, *data.arg3()->GetDict().FindString("subheader"));
EXPECT_EQ(static_cast<int>(state),
*data.arg3()->GetDict().FindInt("state"));
}
void ValidateHandleGetSafetyHubHasRecommendations(bool hasRecommendations) {
base::Value::List args;
args.Append("getSafetyHubHasRecommendations");
handler()->HandleGetSafetyHubHasRecommendations(args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
EXPECT_EQ("cr.webUIResponse", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ("getSafetyHubHasRecommendations", data.arg1()->GetString());
// arg2 is a boolean that is true if the callback is successful.
ASSERT_TRUE(data.arg2()->is_bool());
ASSERT_TRUE(data.arg2());
ASSERT_TRUE(data.arg3()->is_bool());
EXPECT_EQ(hasRecommendations, data.arg3()->GetBool());
}
// For a given Safety Hub module, configure the test environment so that tests
// can run when there is a recommendation for the module and when there is
// not.
void SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule module,
bool isModuleRecommended) {
switch (module) {
case SafetyHubHandler::SafetyHubModule::kPasswords:
isModuleRecommended ? CreateLeakedCredential() : FixLeakedCredential();
break;
case SafetyHubHandler::SafetyHubModule::kVersion:
isModuleRecommended
? g_browser_process->GetBuildState()->SetUpdate(
BuildState::UpdateType::kNormalUpdate,
base::Version({CHROME_VERSION_MAJOR, CHROME_VERSION_MINOR,
CHROME_VERSION_BUILD,
CHROME_VERSION_PATCH + 1}),
std::nullopt)
: g_browser_process->GetBuildState()->SetUpdate(
BuildState::UpdateType::kNone, base::Version(), std::nullopt);
break;
case SafetyHubHandler::SafetyHubModule::kSafeBrowsing:
isModuleRecommended
? SetPrefsForSafeBrowsing(false, false, SettingManager::USER)
: SetPrefsForSafeBrowsing(true, true, SettingManager::USER);
break;
case SafetyHubHandler::SafetyHubModule::kExtensions:
isModuleRecommended ? AddTriggeringExtension()
: ClearTriggeringExtensions();
break;
case SafetyHubHandler::SafetyHubModule::kNotifications:
hcsm()->ClearSettingsForOneType(
ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW);
isModuleRecommended
? AddNotificationPermissionsForReview()
: handler()->HandleIgnoreOriginsForNotificationPermissionReview(
base::Value::List().Append(GetOriginList(1)));
break;
case SafetyHubHandler::SafetyHubModule::kUnusedSitePermissions:
isModuleRecommended
? AddRevokedPermission()
: handler()->HandleAcknowledgeRevokedUnusedSitePermissionsList(
base::Value::List());
break;
default:
NOTREACHED()
<< "Unexpected SafetyHubModule for test setup. A proper setup for "
"the module can be done only for supported modules.\n";
}
}
void ClearTriggeringExtensions() {
handler_->ClearExtensionResultsForTesting();
}
void ValidateEntryPointSubheader(
std::string subheader,
std::optional<SafetyHubHandler::SafetyHubModule> module = std::nullopt) {
// If there is a module provided, expect the module to be in a subheader.
// For that, setup the test environment so that the module has a
// recommendation.
if (module.has_value()) {
SetupTestToShowOrHideRecommendationForModule(module.value(), true);
}
// Send a message to handler to get the current state of the subheader.
base::Value::List args;
args.Append("getSafetyHubEntryPointSubheader");
handler()->HandleGetSafetyHubEntryPointSubheader(args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
// Check that response from the handler follows the right format.
EXPECT_EQ("cr.webUIResponse", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ("getSafetyHubEntryPointSubheader", data.arg1()->GetString());
// arg2 is a boolean that is true if the callback is successful.
ASSERT_TRUE(data.arg2()->is_bool());
ASSERT_TRUE(data.arg2());
// Validate that the subheader we get is equal to the one we expect.
ASSERT_TRUE(data.arg3()->is_string());
EXPECT_EQ(subheader, data.arg3()->GetString());
// If in the beginning of the method the test environment is set for a
// module to have a recommendation, reset that back.
if (module.has_value()) {
SetupTestToShowOrHideRecommendationForModule(module.value(), false);
}
}
base::Value::List GetOriginList(int size) {
base::Value::List origins;
for (int i = 0; i < size; i++) {
origins.Append("https://example" + base::NumberToString(i) + ".org:443");
}
return origins;
}
// TODO(crbug.com/1443466): Consider moving common test util functions between
// this file and password_status_check_service_unittest.cc to a util class.
password_manager::PasswordForm MakeForm(base::StringPiece16 username,
base::StringPiece16 password,
std::string origin = kUsedTestSite,
bool is_leaked = false) {
password_manager::PasswordForm form;
form.username_value = username;
form.password_value = password;
form.signon_realm = origin;
form.url = GURL(origin);
if (is_leaked) {
// Credential issues for weak and reused are detected automatically and
// don't need to be specified explicitly.
form.password_issues.insert_or_assign(
password_manager::InsecureType::kLeaked,
password_manager::InsecurityMetadata(
base::Time::Now(), password_manager::IsMuted(false),
password_manager::TriggerBackendNotification(false)));
}
return form;
}
void RunUntilIdle() { task_environment_.RunUntilIdle(); }
TestingProfile* profile() { return &profile_; }
content::TestWebUI* web_ui() { return &web_ui_; }
SafetyHubHandler* handler() { return handler_.get(); }
HostContentSettingsMap* hcsm() { return hcsm_.get(); }
base::SimpleTestClock* clock() { return &clock_; }
password_manager::TestPasswordStore& profile_store() {
return *profile_store_;
}
password_manager::TestPasswordStore& account_store() {
return *account_store_;
}
private:
base::test::ScopedFeatureList feature_list_;
content::BrowserTaskEnvironment task_environment_;
TestingProfile profile_;
content::TestWebUI web_ui_;
scoped_refptr<HostContentSettingsMap> hcsm_;
base::SimpleTestClock clock_;
scoped_refptr<TestPasswordStore> profile_store_ =
CreateAndUseTestPasswordStore(&profile_);
scoped_refptr<password_manager::TestPasswordStore> account_store_ =
CreateAndUseTestAccountPasswordStore(&profile_);
std::unique_ptr<SafetyHubHandler> handler_;
};
TEST_F(SafetyHubHandlerTest, PopulateUnusedSitePermissionsData) {
// Add GEOLOCATION setting for url but do not add to revoked list.
content_settings::ContentSettingConstraints constraint;
constraint.set_track_last_visit_for_autoexpiration(true);
hcsm()->SetContentSettingDefaultScope(
GURL(kUsedTestSite), GURL(kUsedTestSite),
ContentSettingsType::GEOLOCATION, ContentSetting::CONTENT_SETTING_ALLOW,
constraint);
// Revoked permissions list should still only contain the initial unused site.
const auto& revoked_permissions =
handler()->PopulateUnusedSitePermissionsData();
EXPECT_EQ(revoked_permissions.size(), 1UL);
EXPECT_EQ(GURL(kUnusedTestSite),
GURL(*revoked_permissions[0].GetDict().FindString(
site_settings::kOrigin)));
}
TEST_F(SafetyHubHandlerTest, HandleAllowPermissionsAgainForUnusedSite) {
base::Value::List initial_unused_site_permissions =
handler()->PopulateUnusedSitePermissionsData();
ExpectRevokedPermission();
// Allow the revoked permission for the unused site again.
base::Value::List args;
args.Append(base::Value(kUnusedTestSite));
handler()->HandleAllowPermissionsAgainForUnusedSite(args);
// Check there is no origin in revoked permissions list.
ContentSettingsForOneType revoked_permissions_list =
hcsm()->GetSettingsForOneType(
ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS);
EXPECT_EQ(0U, revoked_permissions_list.size());
// Check if the permissions of url is regranted.
EXPECT_EQ(
ContentSetting::CONTENT_SETTING_ALLOW,
hcsm()->GetContentSetting(GURL(kUnusedTestSite), GURL(kUnusedTestSite),
kUnusedRegularPermission));
EXPECT_EQ(
base::Value::Dict().Set("foo", "bar"),
hcsm()->GetWebsiteSetting(GURL(kUnusedTestSite), GURL(kUnusedTestSite),
kUnusedChooserPermission));
// Undoing restores the initial state.
handler()->HandleUndoAllowPermissionsAgainForUnusedSite(
std::move(initial_unused_site_permissions));
ExpectRevokedPermission();
}
TEST_F(SafetyHubHandlerTest,
HandleAcknowledgeRevokedUnusedSitePermissionsList) {
const auto& revoked_permissions_before =
handler()->PopulateUnusedSitePermissionsData();
EXPECT_GT(revoked_permissions_before.size(), 0U);
// Acknowledging revoked permissions from unused sites clears the list.
base::Value::List args;
handler()->HandleAcknowledgeRevokedUnusedSitePermissionsList(args);
const auto& revoked_permissions_after =
handler()->PopulateUnusedSitePermissionsData();
EXPECT_EQ(revoked_permissions_after.size(), 0U);
// Undo reverts the list to its initial state.
base::Value::List undo_args;
undo_args.Append(revoked_permissions_before.Clone());
handler()->HandleUndoAcknowledgeRevokedUnusedSitePermissionsList(undo_args);
EXPECT_EQ(revoked_permissions_before,
handler()->PopulateUnusedSitePermissionsData());
}
TEST_F(SafetyHubHandlerTest,
HandleIgnoreOriginsForNotificationPermissionReview) {
HostContentSettingsMap* content_settings =
HostContentSettingsMapFactory::GetForProfile(profile());
ContentSettingsForOneType ignored_patterns =
content_settings->GetSettingsForOneType(
ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW);
ASSERT_EQ(0U, ignored_patterns.size());
base::Value::List args;
args.Append(GetOriginList(1));
handler()->HandleIgnoreOriginsForNotificationPermissionReview(args);
// Check there is 1 origin in ignore list.
ignored_patterns = content_settings->GetSettingsForOneType(
ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW);
ASSERT_EQ(1U, ignored_patterns.size());
ValidateNotificationPermissionUpdate();
}
TEST_F(SafetyHubHandlerTest,
HandleUndoIgnoreOriginsForNotificationPermissionReview) {
base::Value::List args;
args.Append(GetOriginList(1));
handler()->HandleIgnoreOriginsForNotificationPermissionReview(args);
// Check there is 1 origin in ignore list.
HostContentSettingsMap* content_settings =
HostContentSettingsMapFactory::GetForProfile(profile());
ContentSettingsForOneType ignored_patterns =
content_settings->GetSettingsForOneType(
ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW);
ASSERT_EQ(1U, ignored_patterns.size());
// Check there are no origins in ignore list.
handler()->HandleUndoIgnoreOriginsForNotificationPermissionReview(args);
ignored_patterns = content_settings->GetSettingsForOneType(
ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW);
ASSERT_EQ(0U, ignored_patterns.size());
}
TEST_F(SafetyHubHandlerTest, HandleAllowNotificationPermissionForOrigins) {
base::Value::List args;
base::Value::List origins = GetOriginList(2);
args.Append(origins.Clone());
handler()->HandleAllowNotificationPermissionForOrigins(args);
// Check the permission for the two origins is allow.
HostContentSettingsMap* content_settings =
HostContentSettingsMapFactory::GetForProfile(profile());
ContentSettingsForOneType notification_permissions =
content_settings->GetSettingsForOneType(
ContentSettingsType::NOTIFICATIONS);
auto type = content_settings->GetContentSetting(
GURL(origins[0].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS);
ASSERT_EQ(CONTENT_SETTING_ALLOW, type);
type = content_settings->GetContentSetting(
GURL(origins[1].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS);
ASSERT_EQ(CONTENT_SETTING_ALLOW, type);
ValidateNotificationPermissionUpdate();
}
TEST_F(SafetyHubHandlerTest, HandleBlockNotificationPermissionForOrigins) {
base::Value::List args;
base::Value::List origins = GetOriginList(2);
args.Append(origins.Clone());
handler()->HandleBlockNotificationPermissionForOrigins(args);
// Check the permission for the two origins is block.
HostContentSettingsMap* content_settings =
HostContentSettingsMapFactory::GetForProfile(profile());
ContentSettingsForOneType notification_permissions =
content_settings->GetSettingsForOneType(
ContentSettingsType::NOTIFICATIONS);
auto type = content_settings->GetContentSetting(
GURL(origins[0].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS);
ASSERT_EQ(CONTENT_SETTING_BLOCK, type);
type = content_settings->GetContentSetting(
GURL(origins[1].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS);
ASSERT_EQ(CONTENT_SETTING_BLOCK, type);
ValidateNotificationPermissionUpdate();
}
TEST_F(SafetyHubHandlerTest, HandleResetNotificationPermissionForOrigins) {
HostContentSettingsMap* content_settings =
HostContentSettingsMapFactory::GetForProfile(profile());
base::Value::List args;
base::Value::List origins = GetOriginList(1);
args.Append(origins.Clone());
content_settings->SetContentSettingCustomScope(
ContentSettingsPattern::FromString(origins[0].GetString()),
ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS,
CONTENT_SETTING_ALLOW);
handler()->HandleResetNotificationPermissionForOrigins(args);
// Check the permission for the origin is reset.
auto type = content_settings->GetContentSetting(
GURL(origins[0].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS);
ASSERT_EQ(CONTENT_SETTING_ASK, type);
ValidateNotificationPermissionUpdate();
}
TEST_F(SafetyHubHandlerTest, HandleGetSafeBrowsingCardData_EnabledEnhanced) {
SetPrefsForSafeBrowsing(true, true, SettingManager::USER);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_SUBHEADER),
SafetyHubCardState::kSafe);
SetPrefsForSafeBrowsing(true, true, SettingManager::EXTENSION);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_SUBHEADER),
SafetyHubCardState::kSafe);
SetPrefsForSafeBrowsing(true, true, SettingManager::ADMIN);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_SUBHEADER),
SafetyHubCardState::kSafe);
}
TEST_F(SafetyHubHandlerTest, HandleGetSafeBrowsingCardData_EnabledStandard) {
SetPrefsForSafeBrowsing(true, false, SettingManager::USER);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_SUBHEADER),
SafetyHubCardState::kSafe);
SetPrefsForSafeBrowsing(true, false, SettingManager::EXTENSION);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_SUBHEADER),
SafetyHubCardState::kSafe);
SetPrefsForSafeBrowsing(true, false, SettingManager::ADMIN);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_SUBHEADER),
SafetyHubCardState::kSafe);
}
TEST_F(SafetyHubHandlerTest, HandleGetSafeBrowsingCardData_DisabledByAdmin) {
SetPrefsForSafeBrowsing(false, false, SettingManager::ADMIN);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_OFF_MANAGED_SUBHEADER),
SafetyHubCardState::kInfo);
SetPrefsForSafeBrowsing(false, true, SettingManager::ADMIN);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_OFF_MANAGED_SUBHEADER),
SafetyHubCardState::kInfo);
}
TEST_F(SafetyHubHandlerTest,
HandleGetSafeBrowsingCardData_DisabledByExtension) {
SetPrefsForSafeBrowsing(false, false, SettingManager::EXTENSION);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_OFF_EXTENSION_SUBHEADER),
SafetyHubCardState::kInfo);
SetPrefsForSafeBrowsing(false, true, SettingManager::EXTENSION);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER),
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SB_OFF_EXTENSION_SUBHEADER),
SafetyHubCardState::kInfo);
}
TEST_F(SafetyHubHandlerTest, HandleGetSafeBrowsingCardData_DisabledByUser) {
SetPrefsForSafeBrowsing(false, false, SettingManager::USER);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER),
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_USER_SUBHEADER),
SafetyHubCardState::kWarning);
SetPrefsForSafeBrowsing(false, true, SettingManager::USER);
ValidateHandleSafeBrowsingCardData(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER),
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_SB_OFF_USER_SUBHEADER),
SafetyHubCardState::kWarning);
}
// Test that revocation is happen correctly for all content setting types.
TEST_F(SafetyHubHandlerTest, RevokeAllContentSettingTypes) {
// TODO(crbug.com/1459305): Remove this after adding names for those
// types.
static constexpr auto kNoNameTypes =
base::MakeFixedFlatSet<ContentSettingsType>({
// clang-format off
ContentSettingsType::MIDI,
ContentSettingsType::DURABLE_STORAGE,
ContentSettingsType::ACCESSIBILITY_EVENTS,
ContentSettingsType::NFC,
ContentSettingsType::FILE_SYSTEM_READ_GUARD,
ContentSettingsType::CAMERA_PAN_TILT_ZOOM,
ContentSettingsType::TOP_LEVEL_STORAGE_ACCESS,
ContentSettingsType::FILE_SYSTEM_ACCESS_EXTENDED_PERMISSION,
// clang-format on
});
// Add all content settings in the content setting registry to revoked
// permissions list.
auto* content_settings_registry =
content_settings::ContentSettingsRegistry::GetInstance();
for (const content_settings::ContentSettingsInfo* info :
*content_settings_registry) {
ContentSettingsType type = info->website_settings_info()->type();
// If the permission can not be tracked, then also can not be revoked.
if (!content_settings::CanTrackLastVisit(type)) {
continue;
}
// If the permission can not set to ALLOW, then also can not be revoked.
if (!content_settings_registry->Get(type)->IsSettingValid(
ContentSetting::CONTENT_SETTING_ALLOW)) {
continue;
}
// Add the permission to revoked permission list.
auto dict = base::Value::Dict().Set(
permissions::kRevokedKey,
base::Value::List().Append(static_cast<int32_t>(type)));
hcsm()->SetWebsiteSettingDefaultScope(
GURL(kUnusedTestSite), GURL(kUnusedTestSite),
ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
base::Value(dict.Clone()));
// Unless the permission in kNoNameTypes, it should be shown on the UI.
const auto& revoked_permissions =
handler()->PopulateUnusedSitePermissionsData();
if (base::Contains(kNoNameTypes, type)) {
EXPECT_EQ(revoked_permissions.size(), 0U);
} else {
EXPECT_EQ(revoked_permissions.size(), 1U);
}
}
}
TEST_F(SafetyHubHandlerTest, VersionCardUpToDate) {
base::Value::List args;
args.Append("getVersionCardData");
handler()->HandleGetVersionCardData(args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
ASSERT_TRUE(data.arg3()->is_dict());
EXPECT_EQ(l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_HUB_VERSION_CARD_HEADER_UPDATED),
base::UTF8ToUTF16(*data.arg3()->GetDict().FindString("header")));
EXPECT_EQ(VersionUI::GetAnnotatedVersionStringForUi(),
base::UTF8ToUTF16(*data.arg3()->GetDict().FindString("subheader")));
EXPECT_EQ(static_cast<int>(SafetyHubCardState::kSafe),
*data.arg3()->GetDict().FindInt("state"));
}
TEST_F(SafetyHubHandlerTest, VersionCardOutOfDate) {
// An update is available, the version card should let the user know.
g_browser_process->GetBuildState()->SetUpdate(
BuildState::UpdateType::kNormalUpdate,
base::Version({CHROME_VERSION_MAJOR, CHROME_VERSION_MINOR,
CHROME_VERSION_BUILD, CHROME_VERSION_PATCH + 1}),
std::nullopt);
base::Value::List args;
args.Append("getVersionCardData");
handler()->HandleGetVersionCardData(args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
ASSERT_TRUE(data.arg3()->is_dict());
EXPECT_EQ(l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_HUB_VERSION_CARD_HEADER_RESTART),
base::UTF8ToUTF16(*data.arg3()->GetDict().FindString("header")));
EXPECT_EQ(l10n_util ::GetStringUTF16(
IDS_SETTINGS_SAFETY_HUB_VERSION_CARD_SUBHEADER_RESTART),
base::UTF8ToUTF16(*data.arg3()->GetDict().FindString("subheader")));
EXPECT_EQ(static_cast<int>(SafetyHubCardState::kWarning),
*data.arg3()->GetDict().FindInt("state"));
}
TEST_F(SafetyHubHandlerTest, HandleGetSafetyHubHasRecommendations) {
std::vector<SafetyHubHandler::SafetyHubModule> modules;
modules.push_back(SafetyHubHandler::SafetyHubModule::kPasswords);
modules.push_back(SafetyHubHandler::SafetyHubModule::kVersion);
modules.push_back(SafetyHubHandler::SafetyHubModule::kSafeBrowsing);
modules.push_back(SafetyHubHandler::SafetyHubModule::kExtensions);
modules.push_back(SafetyHubHandler::SafetyHubModule::kNotifications);
modules.push_back(SafetyHubHandler::SafetyHubModule::kUnusedSitePermissions);
std::vector<int> masks;
for (int i = 0; i < (int)modules.size(); i++) {
masks.push_back(pow(2, i));
}
// Each module can either have a recommendation or not. To test all possible
// combinations of modules, a binary vector and bit masking is used. i-th
// element of the vector represents whether the i-th module in modules array
// has a recommendation or not.
for (int testCase = pow(2, (int)modules.size()) - 1; testCase >= 0;
testCase--) {
SCOPED_TRACE("testCase: " + std::bitset<8>(testCase).to_string());
std::set<SafetyHubHandler::SafetyHubModule> recommendedModules;
for (int i = 0; i < (int)modules.size(); i++) {
bool isModuleRecommended = (testCase & masks[i]);
SetupTestToShowOrHideRecommendationForModule(modules[i],
isModuleRecommended);
if (isModuleRecommended) {
recommendedModules.insert(modules[i]);
}
}
ValidateHandleGetSafetyHubHasRecommendations(!recommendedModules.empty());
}
}
TEST_F(SafetyHubHandlerTest,
HandleGetSafetyHubEntryPointSubheader_NothingToDo) {
// Reset unused site permissions data that is set in the test suite setup.
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kUnusedSitePermissions, false);
ValidateEntryPointSubheader(l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_ENTRY_POINT_NOTHING_TO_DO));
}
TEST_F(SafetyHubHandlerTest, HandleGetSafetyHubEntryPointSubheader_OneModule) {
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kUnusedSitePermissions, false);
ValidateEntryPointSubheader(
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME),
SafetyHubHandler::SafetyHubModule::kPasswords);
ValidateEntryPointSubheader(
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_VERSION_MODULE_UPPERCASE_NAME),
SafetyHubHandler::SafetyHubModule::kVersion);
ValidateEntryPointSubheader(
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SAFE_BROWSING_MODULE_NAME),
SafetyHubHandler::SafetyHubModule::kSafeBrowsing);
ValidateEntryPointSubheader(
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MODULE_UPPERCASE_NAME),
SafetyHubHandler::SafetyHubModule::kExtensions);
ValidateEntryPointSubheader(
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_NOTIFICATIONS_MODULE_UPPERCASE_NAME),
SafetyHubHandler::SafetyHubModule::kNotifications);
ValidateEntryPointSubheader(
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_PERMISSIONS_MODULE_UPPERCASE_NAME),
SafetyHubHandler::SafetyHubModule::kUnusedSitePermissions);
}
TEST_F(SafetyHubHandlerTest,
HandleGetSafetyHubEntryPointSubheader_TwoModulesWithPassword) {
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kUnusedSitePermissions, false);
// Passwords module will always be in a subheader string.
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kPasswords, true);
std::string expected_subheader = "";
// The expected subheader should be "Passwords, Chrome update"
expected_subheader =
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_VERSION_MODULE_LOWERCASE_NAME);
ValidateEntryPointSubheader(expected_subheader,
SafetyHubHandler::SafetyHubModule::kVersion);
// The expected subheader should be "Passwords, Safe Browsing"
expected_subheader =
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SAFE_BROWSING_MODULE_NAME);
ValidateEntryPointSubheader(expected_subheader,
SafetyHubHandler::SafetyHubModule::kSafeBrowsing);
// The expected subheader should be "Passwords, extensions"
expected_subheader =
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MODULE_LOWERCASE_NAME);
ValidateEntryPointSubheader(expected_subheader,
SafetyHubHandler::SafetyHubModule::kExtensions);
// The expected subheader should be "Passwords, notifications"
expected_subheader =
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_NOTIFICATIONS_MODULE_LOWERCASE_NAME);
ValidateEntryPointSubheader(
expected_subheader, SafetyHubHandler::SafetyHubModule::kNotifications);
// The expected subheader should be "Passwords, permissions"
expected_subheader =
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_PERMISSIONS_MODULE_LOWERCASE_NAME);
ValidateEntryPointSubheader(
expected_subheader,
SafetyHubHandler::SafetyHubModule::kUnusedSitePermissions);
}
TEST_F(SafetyHubHandlerTest, HandleGetSafetyHubEntryPointSubheader_AllModules) {
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kPasswords, true);
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kVersion, true);
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kSafeBrowsing, true);
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kExtensions, true);
SetupTestToShowOrHideRecommendationForModule(
SafetyHubHandler::SafetyHubModule::kNotifications, true);
// The expected subheader should be "Passwords, Chrome update, Safe Browsing,
// extensions, notifications, permissions"
std::string expected_subheader =
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_VERSION_MODULE_LOWERCASE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_SAFE_BROWSING_MODULE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MODULE_LOWERCASE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_NOTIFICATIONS_MODULE_LOWERCASE_NAME) +
l10n_util::GetStringUTF8(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR) +
" " +
l10n_util::GetStringUTF8(
IDS_SETTINGS_SAFETY_HUB_PERMISSIONS_MODULE_LOWERCASE_NAME);
ValidateEntryPointSubheader(expected_subheader);
}
TEST_F(SafetyHubHandlerTest, ExtensionPrefAndInitialization) {
// Create fake extensions for our pref service to load and test that
// the `web_ui()` has recorded no events
AddExtensionsForReview();
EXPECT_EQ(5, handler()->GetNumberOfExtensionsThatNeedReview());
EXPECT_EQ(0u, web_ui()->call_data().size());
// After `AcknowledgeSafetyCheckExtensions` one event should have been fired.
safety_hub_test_util::AcknowledgeSafetyCheckExtensions(
crx_file::id_util::GenerateId("TestExtension5"), profile());
EXPECT_EQ(1u, web_ui()->call_data().size());
// After `RemoveExtension` one additional event should have been fired
// making two total events.
safety_hub_test_util::AcknowledgeSafetyCheckExtensions(
crx_file::id_util::GenerateId("TestExtension4"), profile());
EXPECT_EQ(2u, web_ui()->call_data().size());
// When the same actions are preformed on good extensions, no more
// events are fired.
safety_hub_test_util::AcknowledgeSafetyCheckExtensions(
crx_file::id_util::GenerateId("lgcbbihjdcmnlndohpfhppljiilnkoab"),
profile());
EXPECT_EQ(2u, web_ui()->call_data().size());
safety_hub_test_util::RemoveExtension("jadkojfancihcakelhdnpkcidencgdjg",
ManifestLocation::kInternal, profile());
EXPECT_EQ(2u, web_ui()->call_data().size());
}