| // 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 <memory> |
| |
| #include "base/check.h" |
| #include "base/json/values_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/time/default_clock.h" |
| #include "base/time/time.h" |
| #include "base/values.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/profiles/profile.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ui/safety_hub/extensions_result.h" |
| #include "chrome/browser/ui/safety_hub/menu_notification_service_factory.h" |
| #include "chrome/browser/ui/safety_hub/notification_permission_review_service.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/unused_site_permissions_service.h" |
| #include "chrome/browser/ui/safety_hub/unused_site_permissions_service_factory.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/grit/branded_strings.h" |
| #include "chrome/grit/generated_resources.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/features.h" |
| #include "components/password_manager/core/common/password_manager_pref_names.h" |
| #include "components/permissions/constants.h" |
| #include "components/safe_browsing/core/common/safe_browsing_prefs.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/site_engagement/content/site_engagement_service.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_prefs_factory.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/manifest.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/l10n/time_format.h" |
| #include "url/gurl.h" |
| |
| using extensions::ExtensionPrefs; |
| using extensions::ExtensionRegistry; |
| using safety_hub::SafetyHubCardState; |
| |
| namespace { |
| // Key of the expiration time in the |UnusedSitePermissions| object. Indicates |
| // the time after which the associated origin and permissions are no longer |
| // shown in the UI. |
| constexpr char kExpirationKey[] = "expiration"; |
| // Key of the lifetime in the |UnusedSitePermissions| object. |
| constexpr char kLifetimeKey[] = "lifetime"; |
| |
| // Get values from |UnusedSitePermission| object in |
| // safety_hub_browser_proxy.ts. |
| SafetyHubHandler::PermissionsData GetUnusedSitePermissionsFromDict( |
| const base::Value::Dict& unused_site_permissions) { |
| SafetyHubHandler::PermissionsData permissions_data; |
| const std::string* origin_str = |
| unused_site_permissions.FindString(site_settings::kOrigin); |
| CHECK(origin_str); |
| const auto url = GURL(*origin_str); |
| CHECK(url.is_valid()); |
| permissions_data.origin = url::Origin::Create(url); |
| |
| const base::Value::List* permissions = |
| unused_site_permissions.FindList(site_settings::kPermissions); |
| CHECK(permissions); |
| for (const auto& permission : *permissions) { |
| CHECK(permission.is_string()); |
| const std::string& type_string = permission.GetString(); |
| ContentSettingsType type = |
| site_settings::ContentSettingsTypeFromGroupName(type_string); |
| CHECK(type != ContentSettingsType::DEFAULT) |
| << type_string << " is not expected to have a UI representation."; |
| permissions_data.permissions.insert(type); |
| } |
| |
| const base::Value::Dict* chooser_permissions_data = |
| unused_site_permissions.FindDict( |
| safety_hub::kSafetyHubChooserPermissionsData); |
| permissions_data.chooser_permissions_data = |
| chooser_permissions_data ? chooser_permissions_data->Clone() |
| : base::Value::Dict(); |
| |
| const base::Value* js_expiration = |
| unused_site_permissions.Find(kExpirationKey); |
| CHECK(js_expiration); |
| base::Time expiration = base::ValueToTime(js_expiration).value(); |
| |
| const base::Value* js_lifetime = unused_site_permissions.Find(kLifetimeKey); |
| // Users may edit the stored fields directly, so we cannot assume their |
| // presence and validity. |
| base::TimeDelta lifetime = content_settings::RuleMetaData::ComputeLifetime( |
| /*lifetime=*/ |
| base::ValueToTimeDelta(js_lifetime).value_or(base::TimeDelta()), |
| /*expiration=*/expiration); |
| |
| permissions_data.constraints = |
| content_settings::ContentSettingConstraints(expiration - lifetime); |
| permissions_data.constraints.set_lifetime(lifetime); |
| |
| return permissions_data; |
| } |
| |
| // Returns the state of Safe Browsing setting. |
| SafeBrowsingState GetSafeBrowsingState(PrefService* pref_service) { |
| // TODO(crbug.com/1443466): Use SafeBrowsingResult from Safety Hub instead. |
| if (safe_browsing::IsEnhancedProtectionEnabled(*pref_service)) |
| return SafeBrowsingState::kEnabledEnhanced; |
| if (safe_browsing::IsSafeBrowsingEnabled(*pref_service)) |
| return SafeBrowsingState::kEnabledStandard; |
| if (safe_browsing::IsSafeBrowsingPolicyManaged(*pref_service)) |
| return SafeBrowsingState::kDisabledByAdmin; |
| if (safe_browsing::IsSafeBrowsingExtensionControlled(*pref_service)) |
| return SafeBrowsingState::kDisabledByExtension; |
| return SafeBrowsingState::kDisabledByUser; |
| } |
| |
| base::Value::Dict CardDataToValue(int header_id, |
| int subheader_id, |
| SafetyHubCardState card_state) { |
| base::Value::Dict sb_card_info; |
| |
| sb_card_info.Set(safety_hub::kCardHeaderKey, |
| l10n_util::GetStringUTF16(header_id)); |
| sb_card_info.Set(safety_hub::kCardSubheaderKey, |
| l10n_util::GetStringUTF16(subheader_id)); |
| sb_card_info.Set(safety_hub::kCardStateKey, static_cast<int>(card_state)); |
| |
| return sb_card_info; |
| } |
| |
| // Returns true if the card dict indicates there is something actionable for the |
| // user. |
| bool CardHasRecommendations(base::Value::Dict card_data) { |
| std::optional<int> state = card_data.FindInt(safety_hub::kCardStateKey); |
| CHECK(state.has_value()); |
| SafetyHubCardState card_state = |
| static_cast<SafetyHubCardState>(state.value()); |
| |
| return card_state == SafetyHubCardState::kWarning || |
| card_state == SafetyHubCardState::kWeak; |
| } |
| |
| void AppendModuleNameToString(std::u16string& str, |
| int uppercase_id, |
| int lowercase_id = 0) { |
| if (str.empty()) { |
| str.append(l10n_util::GetStringUTF16(uppercase_id)); |
| return; |
| } |
| |
| if (lowercase_id == 0) { |
| lowercase_id = uppercase_id; |
| } |
| |
| str.append( |
| l10n_util::GetStringUTF16(IDS_SETTINGS_SAFETY_HUB_MODULE_NAME_SEPARATOR)); |
| str.append(u" "); |
| str.append(l10n_util::GetStringUTF16(lowercase_id)); |
| } |
| } // namespace |
| |
| SafetyHubHandler::SafetyHubHandler(Profile* profile) |
| : profile_(profile), clock_(base::DefaultClock::GetInstance()) { |
| prefs_observation_.Observe(ExtensionPrefs::Get(profile_)); |
| extension_registry_observation_.Observe(ExtensionRegistry::Get(profile_)); |
| } |
| SafetyHubHandler::~SafetyHubHandler() = default; |
| |
| SafetyHubHandler::PermissionsData::PermissionsData() = default; |
| SafetyHubHandler::PermissionsData::~PermissionsData() = default; |
| SafetyHubHandler::PermissionsData::PermissionsData(PermissionsData&&) = default; |
| SafetyHubHandler::PermissionsData& SafetyHubHandler::PermissionsData::operator=( |
| PermissionsData&&) = default; |
| |
| // static |
| std::unique_ptr<SafetyHubHandler> SafetyHubHandler::GetForProfile( |
| Profile* profile) { |
| return std::make_unique<SafetyHubHandler>(profile); |
| } |
| |
| void SafetyHubHandler::HandleGetRevokedUnusedSitePermissionsList( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(1U, args.size()); |
| const base::Value& callback_id = args[0]; |
| |
| base::Value::List result = PopulateUnusedSitePermissionsData(); |
| |
| ResolveJavascriptCallback(callback_id, base::Value(std::move(result))); |
| } |
| |
| void SafetyHubHandler::HandleAllowPermissionsAgainForUnusedSite( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| CHECK(args[0].is_string()); |
| const std::string& origin_str = args[0].GetString(); |
| |
| UnusedSitePermissionsService* service = |
| UnusedSitePermissionsServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| |
| url::Origin origin = url::Origin::Create(GURL(origin_str)); |
| |
| service->RegrantPermissionsForOrigin(origin); |
| SendUnusedSitePermissionsReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleUndoAllowPermissionsAgainForUnusedSite( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| CHECK(args[0].is_dict()); |
| |
| PermissionsData permissions_data = |
| GetUnusedSitePermissionsFromDict(args[0].GetDict()); |
| UnusedSitePermissionsService* service = |
| UnusedSitePermissionsServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| |
| service->UndoRegrantPermissionsForOrigin( |
| permissions_data.permissions, permissions_data.chooser_permissions_data, |
| permissions_data.constraints, permissions_data.origin); |
| |
| SendUnusedSitePermissionsReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleAcknowledgeRevokedUnusedSitePermissionsList( |
| const base::Value::List& args) { |
| UnusedSitePermissionsService* service = |
| UnusedSitePermissionsServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| service->ClearRevokedPermissionsList(); |
| |
| SendUnusedSitePermissionsReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleUndoAcknowledgeRevokedUnusedSitePermissionsList( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| CHECK(args[0].is_list()); |
| |
| const base::Value::List& unused_site_permissions_list = args[0].GetList(); |
| UnusedSitePermissionsService* service = |
| UnusedSitePermissionsServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| |
| for (const auto& unused_site_permissions_js : unused_site_permissions_list) { |
| CHECK(unused_site_permissions_js.is_dict()); |
| PermissionsData permissions_data = |
| GetUnusedSitePermissionsFromDict(unused_site_permissions_js.GetDict()); |
| service->StorePermissionInRevokedPermissionSetting( |
| permissions_data.permissions, permissions_data.chooser_permissions_data, |
| permissions_data.constraints, permissions_data.origin); |
| } |
| |
| SendUnusedSitePermissionsReviewList(); |
| } |
| |
| base::Value::List SafetyHubHandler::PopulateUnusedSitePermissionsData() { |
| base::Value::List result; |
| if (!base::FeatureList::IsEnabled( |
| content_settings::features::kSafetyCheckUnusedSitePermissions)) { |
| return result; |
| } |
| |
| HostContentSettingsMap* hcsm = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| |
| for (const auto& revoked_permissions : hcsm->GetSettingsForOneType( |
| ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS)) { |
| base::Value::Dict revoked_permission_value; |
| revoked_permission_value.Set( |
| site_settings::kOrigin, revoked_permissions.primary_pattern.ToString()); |
| const base::Value& stored_value = revoked_permissions.setting_value; |
| DCHECK(stored_value.is_dict()); |
| |
| // The revoked permissions list should be reachable by given key. |
| DCHECK(stored_value.GetDict().FindList(permissions::kRevokedKey)); |
| |
| auto type_list = |
| stored_value.GetDict().FindList(permissions::kRevokedKey)->Clone(); |
| base::Value::List permissions_value_list; |
| for (base::Value& type : type_list) { |
| base::StringPiece permission_str = |
| site_settings::ContentSettingsTypeToGroupName( |
| static_cast<ContentSettingsType>(type.GetInt())); |
| if (!permission_str.empty()) { |
| permissions_value_list.Append(permission_str); |
| } |
| } |
| |
| // Some permissions have no readable name, although Safety Hub revokes them. |
| // To prevent crashes, if there is no permission to be shown in the UI, the |
| // origin will not be added to the revoked permissions list. |
| // TODO(crbug.com/1459305): Remove this after adding check for |
| // ContentSettingsTypeToGroupName. |
| if (permissions_value_list.empty()) { |
| continue; |
| } |
| |
| revoked_permission_value.Set( |
| site_settings::kPermissions, |
| base::Value(std::move(permissions_value_list))); |
| |
| revoked_permission_value.Set( |
| kExpirationKey, |
| base::TimeToValue(revoked_permissions.metadata.expiration())); |
| |
| revoked_permission_value.Set( |
| kLifetimeKey, |
| base::TimeDeltaToValue(revoked_permissions.metadata.lifetime())); |
| |
| auto* chooser_permissions_data_dict = stored_value.GetDict().FindDict( |
| permissions::kRevokedChooserPermissionsKey); |
| if (chooser_permissions_data_dict) { |
| revoked_permission_value.Set( |
| safety_hub::kSafetyHubChooserPermissionsData, |
| base::Value(chooser_permissions_data_dict->Clone())); |
| } |
| |
| result.Append(std::move(revoked_permission_value)); |
| } |
| return result; |
| } |
| |
| void SafetyHubHandler::HandleGetNotificationPermissionReviewList( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| const base::Value& callback_id = args[0]; |
| |
| NotificationPermissionsReviewService* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| DCHECK(service); |
| |
| if (!service) { |
| RejectJavascriptCallback(callback_id, base::Value()); |
| } |
| |
| base::Value::List result = |
| service->PopulateNotificationPermissionReviewData(); |
| |
| ResolveJavascriptCallback(callback_id, base::Value(std::move(result))); |
| } |
| |
| void SafetyHubHandler::HandleIgnoreOriginsForNotificationPermissionReview( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| |
| NotificationPermissionsReviewService* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| |
| for (const auto& origin : origins) { |
| const ContentSettingsPattern primary_pattern = |
| ContentSettingsPattern::FromString(origin.GetString()); |
| service->AddPatternToNotificationPermissionReviewBlocklist( |
| primary_pattern, ContentSettingsPattern::Wildcard()); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleResetNotificationPermissionForOrigins( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| |
| const base::Value::List& origins = args[0].GetList(); |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| |
| for (const auto& origin : origins) { |
| map->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromString(origin.GetString()), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_DEFAULT); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleDismissActiveMenuNotification( |
| const base::Value::List& args) { |
| SafetyHubMenuNotificationServiceFactory::GetForProfile(profile_) |
| ->DismissActiveNotification(); |
| } |
| |
| void SafetyHubHandler::HandleDismissPasswordMenuNotification( |
| const base::Value::List& args) { |
| SafetyHubMenuNotificationServiceFactory::GetForProfile(profile_) |
| ->DismissActiveNotificationOfModule( |
| safety_hub::SafetyHubModuleType::PASSWORDS); |
| } |
| |
| void SafetyHubHandler::HandleDismissExtensionsMenuNotification( |
| const base::Value::List& args) { |
| SafetyHubMenuNotificationServiceFactory::GetForProfile(profile_) |
| ->DismissActiveNotificationOfModule( |
| safety_hub::SafetyHubModuleType::EXTENSIONS); |
| } |
| |
| void SafetyHubHandler::HandleBlockNotificationPermissionForOrigins( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| for (const auto& origin : origins) { |
| map->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromString(origin.GetString()), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_BLOCK); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleAllowNotificationPermissionForOrigins( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| |
| for (const auto& origin : origins) { |
| map->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromString(origin.GetString()), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_ALLOW); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleUndoIgnoreOriginsForNotificationPermissionReview( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| NotificationPermissionsReviewService* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| |
| for (const auto& origin : origins) { |
| const ContentSettingsPattern& primary_pattern = |
| ContentSettingsPattern::FromString(origin.GetString()); |
| service->RemovePatternFromNotificationPermissionReviewBlocklist( |
| primary_pattern, ContentSettingsPattern::Wildcard()); |
| } |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SafetyHubHandler::HandleGetSafeBrowsingCardData( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(1U, args.size()); |
| const base::Value& callback_id = args[0]; |
| |
| ResolveJavascriptCallback(callback_id, GetSafeBrowsingCardData()); |
| } |
| |
| void SafetyHubHandler::HandleGetNumberOfExtensionsThatNeedReview( |
| const base::Value::List& args) { |
| const base::Value& callback_id = args[0]; |
| AllowJavascript(); |
| ResolveJavascriptCallback(callback_id, |
| base::Value(GetNumberOfExtensionsThatNeedReview())); |
| } |
| |
| base::Value::Dict SafetyHubHandler::GetSafeBrowsingCardData() { |
| SafeBrowsingState result = GetSafeBrowsingState(profile_->GetPrefs()); |
| |
| base::Value::Dict sb_card_info; |
| |
| switch (result) { |
| case SafeBrowsingState::kEnabledEnhanced: |
| sb_card_info = |
| CardDataToValue(IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_HEADER, |
| IDS_SETTINGS_SAFETY_HUB_SB_ON_ENHANCED_SUBHEADER, |
| SafetyHubCardState::kSafe); |
| break; |
| case SafeBrowsingState::kEnabledStandard: |
| sb_card_info = |
| CardDataToValue(IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_HEADER, |
| IDS_SETTINGS_SAFETY_HUB_SB_ON_STANDARD_SUBHEADER, |
| SafetyHubCardState::kSafe); |
| break; |
| case SafeBrowsingState::kDisabledByAdmin: |
| sb_card_info = |
| CardDataToValue(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER, |
| IDS_SETTINGS_SAFETY_HUB_SB_OFF_MANAGED_SUBHEADER, |
| SafetyHubCardState::kInfo); |
| break; |
| case SafeBrowsingState::kDisabledByExtension: |
| sb_card_info = |
| CardDataToValue(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER, |
| IDS_SETTINGS_SAFETY_HUB_SB_OFF_EXTENSION_SUBHEADER, |
| SafetyHubCardState::kInfo); |
| break; |
| default: |
| sb_card_info = |
| CardDataToValue(IDS_SETTINGS_SAFETY_HUB_SB_OFF_HEADER, |
| IDS_SETTINGS_SAFETY_HUB_SB_OFF_USER_SUBHEADER, |
| SafetyHubCardState::kWarning); |
| } |
| return sb_card_info; |
| } |
| |
| void SafetyHubHandler::HandleGetPasswordCardData( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(1U, args.size()); |
| const base::Value& callback_id = args[0]; |
| |
| ResolveJavascriptCallback(callback_id, base::Value(GetPasswordCardData())); |
| } |
| |
| base::Value::Dict SafetyHubHandler::GetPasswordCardData() { |
| PasswordStatusCheckService* service = |
| PasswordStatusCheckServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| signin::IdentityManager* identity_manager = |
| IdentityManagerFactory::GetForProfile(profile_); |
| bool signed_in = identity_manager && identity_manager->HasPrimaryAccount( |
| signin::ConsentLevel::kSignin); |
| |
| return service->GetPasswordCardData(signed_in); |
| } |
| |
| void SafetyHubHandler::HandleGetVersionCardData(const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(1U, args.size()); |
| const base::Value& callback_id = args[0]; |
| |
| ResolveJavascriptCallback(callback_id, base::Value(GetVersionCardData())); |
| } |
| |
| base::Value::Dict SafetyHubHandler::GetVersionCardData() { |
| base::Value::Dict result; |
| switch (g_browser_process->GetBuildState()->update_type()) { |
| case BuildState::UpdateType::kNone: |
| result.Set(safety_hub::kCardHeaderKey, |
| l10n_util::GetStringUTF16( |
| IDS_SETTINGS_SAFETY_HUB_VERSION_CARD_HEADER_UPDATED)); |
| result.Set(safety_hub::kCardSubheaderKey, |
| VersionUI::GetAnnotatedVersionStringForUi()); |
| result.Set(safety_hub::kCardStateKey, |
| static_cast<int>(SafetyHubCardState::kSafe)); |
| break; |
| case BuildState::UpdateType::kNormalUpdate: |
| // kEnterpriseRollback and kChannelSwitchRollback are fairly rare state, |
| // they will be handled same as there is waiting updates. |
| case BuildState::UpdateType::kEnterpriseRollback: |
| case BuildState::UpdateType::kChannelSwitchRollback: |
| result = CardDataToValue( |
| IDS_SETTINGS_SAFETY_HUB_VERSION_CARD_HEADER_RESTART, |
| IDS_SETTINGS_SAFETY_HUB_VERSION_CARD_SUBHEADER_RESTART, |
| SafetyHubCardState::kWarning); |
| } |
| return result; |
| } |
| |
| void SafetyHubHandler::HandleGetSafetyHubHasRecommendations( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(1U, args.size()); |
| const base::Value& callback_id = args[0]; |
| |
| bool has_recommendations = !GetSafetyHubModulesWithRecommendations().empty(); |
| |
| ResolveJavascriptCallback(callback_id, has_recommendations); |
| } |
| |
| void SafetyHubHandler::HandleGetSafetyHubEntryPointSubheader( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(1U, args.size()); |
| const base::Value& callback_id = args[0]; |
| |
| std::set<SafetyHubModule> modules = GetSafetyHubModulesWithRecommendations(); |
| |
| // If there is no module that needs attention, a static string will be used |
| // for the subheader. |
| if (modules.empty()) { |
| ResolveJavascriptCallback( |
| callback_id, base::Value(l10n_util::GetStringUTF16( |
| IDS_SETTINGS_SAFETY_HUB_ENTRY_POINT_NOTHING_TO_DO))); |
| return; |
| } |
| |
| // Modules in subheader should be added in the following order: Passwords, |
| // Version, Safe Browsing, Extensions, Notifications, Permissions. |
| std::u16string subheader = u""; |
| |
| if (modules.contains(SafetyHubModule::kPasswords)) { |
| AppendModuleNameToString(subheader, |
| IDS_SETTINGS_SAFETY_HUB_PASSWORDS_MODULE_NAME); |
| } |
| |
| if (modules.contains(SafetyHubModule::kVersion)) { |
| AppendModuleNameToString( |
| subheader, IDS_SETTINGS_SAFETY_HUB_VERSION_MODULE_UPPERCASE_NAME, |
| IDS_SETTINGS_SAFETY_HUB_VERSION_MODULE_LOWERCASE_NAME); |
| } |
| |
| if (modules.contains(SafetyHubModule::kSafeBrowsing)) { |
| AppendModuleNameToString(subheader, |
| IDS_SETTINGS_SAFETY_HUB_SAFE_BROWSING_MODULE_NAME); |
| } |
| |
| if (modules.contains(SafetyHubModule::kExtensions)) { |
| AppendModuleNameToString( |
| subheader, IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MODULE_UPPERCASE_NAME, |
| IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MODULE_LOWERCASE_NAME); |
| } |
| |
| if (modules.contains(SafetyHubModule::kNotifications)) { |
| AppendModuleNameToString( |
| subheader, IDS_SETTINGS_SAFETY_HUB_NOTIFICATIONS_MODULE_UPPERCASE_NAME, |
| IDS_SETTINGS_SAFETY_HUB_NOTIFICATIONS_MODULE_LOWERCASE_NAME); |
| } |
| |
| if (modules.contains(SafetyHubModule::kUnusedSitePermissions)) { |
| AppendModuleNameToString( |
| subheader, IDS_SETTINGS_SAFETY_HUB_PERMISSIONS_MODULE_UPPERCASE_NAME, |
| IDS_SETTINGS_SAFETY_HUB_PERMISSIONS_MODULE_LOWERCASE_NAME); |
| } |
| |
| ResolveJavascriptCallback(callback_id, base::Value(subheader)); |
| } |
| |
| std::set<SafetyHubHandler::SafetyHubModule> |
| SafetyHubHandler::GetSafetyHubModulesWithRecommendations() { |
| std::set<SafetyHubModule> modules; |
| |
| // Passwords module |
| if (CardHasRecommendations(GetPasswordCardData())) { |
| modules.insert(SafetyHubModule::kPasswords); |
| } |
| // Version module |
| if (CardHasRecommendations(GetVersionCardData())) { |
| modules.insert(SafetyHubModule::kVersion); |
| } |
| // SafeBrowsing module |
| if (CardHasRecommendations(GetSafeBrowsingCardData())) { |
| modules.insert(SafetyHubModule::kSafeBrowsing); |
| } |
| // Extensions module |
| if (GetNumberOfExtensionsThatNeedReview() > 0) { |
| modules.insert(SafetyHubModule::kExtensions); |
| } |
| // Notifications module |
| NotificationPermissionsReviewService* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| CHECK(service); |
| if (!service->PopulateNotificationPermissionReviewData().empty()) { |
| modules.insert(SafetyHubModule::kNotifications); |
| } |
| // Unused site permission module |
| if (!PopulateUnusedSitePermissionsData().empty()) { |
| modules.insert(SafetyHubModule::kUnusedSitePermissions); |
| } |
| |
| return modules; |
| } |
| |
| void SafetyHubHandler::RegisterMessages() { |
| // Usage of base::Unretained(this) is safe, because web_ui() owns `this` and |
| // won't release ownership until destruction. |
| web_ui()->RegisterMessageCallback( |
| "getRevokedUnusedSitePermissionsList", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleGetRevokedUnusedSitePermissionsList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "allowPermissionsAgainForUnusedSite", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleAllowPermissionsAgainForUnusedSite, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "undoAllowPermissionsAgainForUnusedSite", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleUndoAllowPermissionsAgainForUnusedSite, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "acknowledgeRevokedUnusedSitePermissionsList", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleAcknowledgeRevokedUnusedSitePermissionsList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "undoAcknowledgeRevokedUnusedSitePermissionsList", |
| base::BindRepeating( |
| &SafetyHubHandler:: |
| HandleUndoAcknowledgeRevokedUnusedSitePermissionsList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getNotificationPermissionReview", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleGetNotificationPermissionReviewList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "ignoreNotificationPermissionReviewForOrigins", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleIgnoreOriginsForNotificationPermissionReview, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "resetNotificationPermissionForOrigins", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleResetNotificationPermissionForOrigins, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "dismissActiveMenuNotification", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleDismissActiveMenuNotification, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "dismissSafetyHubPasswordMenuNotification", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleDismissPasswordMenuNotification, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "dismissSafetyHubExtensionsMenuNotification", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleDismissExtensionsMenuNotification, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "blockNotificationPermissionForOrigins", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleBlockNotificationPermissionForOrigins, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "allowNotificationPermissionForOrigins", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleAllowNotificationPermissionForOrigins, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "undoIgnoreNotificationPermissionReviewForOrigins", |
| base::BindRepeating( |
| &SafetyHubHandler:: |
| HandleUndoIgnoreOriginsForNotificationPermissionReview, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getSafeBrowsingCardData", |
| base::BindRepeating(&SafetyHubHandler::HandleGetSafeBrowsingCardData, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getPasswordCardData", |
| base::BindRepeating(&SafetyHubHandler::HandleGetPasswordCardData, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getVersionCardData", |
| base::BindRepeating(&SafetyHubHandler::HandleGetVersionCardData, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getSafetyHubHasRecommendations", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleGetSafetyHubHasRecommendations, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getSafetyHubEntryPointSubheader", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleGetSafetyHubEntryPointSubheader, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getNumberOfExtensionsThatNeedReview", |
| base::BindRepeating( |
| &SafetyHubHandler::HandleGetNumberOfExtensionsThatNeedReview, |
| base::Unretained(this))); |
| } |
| |
| void SafetyHubHandler::SendUnusedSitePermissionsReviewList() { |
| // Notify observers that the unused site permission review list could have |
| // changed. Note that the list is not guaranteed to have changed. In places |
| // where determining whether the list has changed is cause for performance |
| // concerns, an unchanged list may be sent. |
| FireWebUIListener("unused-permission-review-list-maybe-changed", |
| PopulateUnusedSitePermissionsData()); |
| } |
| |
| void SafetyHubHandler::SendNotificationPermissionReviewList() { |
| NotificationPermissionsReviewService* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| if (!service) { |
| return; |
| } |
| |
| // Notify observers that the permission review list could have changed. Note |
| // that the list is not guaranteed to have changed. |
| FireWebUIListener( |
| site_settings::kNotificationPermissionsReviewListMaybeChangedEvent, |
| service->PopulateNotificationPermissionReviewData()); |
| } |
| |
| void SafetyHubHandler::InitSafetyHubExtensionResults() { |
| std::optional<std::unique_ptr<SafetyHubService::Result>> sh_result = |
| SafetyHubExtensionsResult::GetResult( |
| extensions::CWSInfoService::Get(profile_), profile_, false); |
| if (sh_result.has_value()) { |
| extension_sh_result_ = std::make_unique<SafetyHubExtensionsResult>( |
| *static_cast<SafetyHubExtensionsResult*>(sh_result->get())); |
| } |
| } |
| |
| int SafetyHubHandler::GetNumberOfExtensionsThatNeedReview() { |
| if (!extension_sh_result_) { |
| InitSafetyHubExtensionResults(); |
| } |
| if (extension_sh_result_) { |
| return extension_sh_result_->GetNumTriggeringExtensions(); |
| } else { |
| return 0; |
| } |
| } |
| |
| void SafetyHubHandler::UpdateNumberOfExtensionsThatNeedReview( |
| int num_extension_need_review_before, |
| int num_extension_need_review_after) { |
| if (num_extension_need_review_before != num_extension_need_review_after) { |
| AllowJavascript(); |
| FireWebUIListener("extensions-review-list-maybe-changed", |
| num_extension_need_review_after); |
| } |
| } |
| |
| void SafetyHubHandler::OnExtensionPrefsUpdated( |
| const std::string& extension_id) { |
| if (!extension_sh_result_) { |
| return; |
| } |
| int num_extension_need_review_before = GetNumberOfExtensionsThatNeedReview(); |
| extension_sh_result_->OnExtensionPrefsUpdated( |
| extension_id, profile_, extensions::CWSInfoService::Get(profile_)); |
| int num_extension_need_review_after = GetNumberOfExtensionsThatNeedReview(); |
| UpdateNumberOfExtensionsThatNeedReview(num_extension_need_review_before, |
| num_extension_need_review_after); |
| } |
| |
| void SafetyHubHandler::OnExtensionUninstalled( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| extensions::UninstallReason reason) { |
| if (!extension_sh_result_) { |
| return; |
| } |
| int num_extension_need_review_before = GetNumberOfExtensionsThatNeedReview(); |
| extension_sh_result_->OnExtensionUninstalled(browser_context, extension, |
| reason); |
| int num_extension_need_review_after = GetNumberOfExtensionsThatNeedReview(); |
| UpdateNumberOfExtensionsThatNeedReview(num_extension_need_review_before, |
| num_extension_need_review_after); |
| } |
| |
| void SafetyHubHandler::OnExtensionPrefsWillBeDestroyed(ExtensionPrefs* prefs) { |
| DCHECK(prefs_observation_.IsObservingSource(prefs)); |
| prefs_observation_.Reset(); |
| } |
| |
| void SafetyHubHandler::OnShutdown(extensions::ExtensionRegistry* registry) { |
| extension_registry_observation_.Reset(); |
| } |
| |
| void SafetyHubHandler::SetClockForTesting(base::Clock* clock) { |
| clock_ = clock; |
| } |
| |
| void SafetyHubHandler::ClearExtensionResultsForTesting() { |
| GetNumberOfExtensionsThatNeedReview(); |
| if (extension_sh_result_) { |
| extension_sh_result_->ClearTriggeringExtensionsForTesting(); // IN-TEST |
| } |
| } |
| |
| void SafetyHubHandler::SetTriggeringExtensionForTesting( |
| std::string extension_id) { |
| GetNumberOfExtensionsThatNeedReview(); |
| if (extension_sh_result_) { |
| extension_sh_result_->SetTriggeringExtensionForTesting( // IN-TEST |
| extension_id); // IN-TEST |
| } |
| } |
| |
| void SafetyHubHandler::OnJavascriptAllowed() {} |
| |
| void SafetyHubHandler::OnJavascriptDisallowed() {} |