blob: 4f7c6fb8f046bb453011a5eed65cec4350865e25 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/safety_hub/extensions_result.h"
#include <memory>
#include <optional>
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_service.h"
#include "chrome/grit/generated_resources.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_set.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
constexpr extensions::PrefMap kPrefAcknowledgeSafetyCheckWarning = {
"ack_safety_check_warning", extensions::PrefType::kBool,
extensions::PrefScope::kExtensionSpecific};
bool ShouldExtensionBeReviewed(
const extensions::Extension& extension,
const extensions::ExtensionPrefs* extension_prefs,
const extensions::CWSInfoService* extension_info_service,
bool consider_unpublished_only) {
bool warning_acked = false;
extension_prefs->ReadPrefAsBoolean(
extension.id(), kPrefAcknowledgeSafetyCheckWarning, &warning_acked);
bool is_extension = extension.is_extension() || extension.is_shared_module();
// If the user has previously acknowledged the warning on this
// extension and chosen to keep it, we will not show an additional
// Safety Hub warning. We also will not show warnings on Chrome apps.
if (warning_acked || !is_extension) {
return false;
}
std::optional<extensions::CWSInfoService::CWSInfo> extension_info =
extension_info_service->GetCWSInfo(extension);
if (extension_info.has_value() && extension_info->is_present) {
// When only considering extensions that have been unpublished for a long
// time, discard any other review reason. Note that extensions can have
// multiple review reasons, and we're only returning the "main" one.
if (consider_unpublished_only) {
if (extension_info->unpublished_long_ago) {
return true;
}
return false;
}
switch (extension_info->violation_type) {
case extensions::CWSInfoService::CWSViolationType::kMalware:
case extensions::CWSInfoService::CWSViolationType::kPolicy:
return true;
case extensions::CWSInfoService::CWSViolationType::kNone:
case extensions::CWSInfoService::CWSViolationType::kMinorPolicy:
case extensions::CWSInfoService::CWSViolationType::kUnknown:
if (extension_info->unpublished_long_ago) {
return true;
}
break;
}
}
return false;
}
} // namespace
SafetyHubExtensionsResult::SafetyHubExtensionsResult(
std::set<extensions::ExtensionId> triggering_extensions,
bool is_unpublished_extensions_only)
: triggering_extensions_(triggering_extensions),
is_unpublished_extensions_only_(is_unpublished_extensions_only) {}
SafetyHubExtensionsResult::SafetyHubExtensionsResult(
const base::Value::Dict& dict) {
for (const base::Value& extension_id :
*dict.FindList(safety_hub::kSafetyHubTriggeringExtensionIdsKey)) {
triggering_extensions_.insert(extension_id.GetString());
}
// Only results that contain unpublished extensions should be created with
// this constructor.
is_unpublished_extensions_only_ = true;
}
SafetyHubExtensionsResult::SafetyHubExtensionsResult(
const SafetyHubExtensionsResult&) = default;
SafetyHubExtensionsResult& SafetyHubExtensionsResult::operator=(
const SafetyHubExtensionsResult&) = default;
SafetyHubExtensionsResult::~SafetyHubExtensionsResult() = default;
// static
std::optional<std::unique_ptr<SafetyHubService::Result>>
SafetyHubExtensionsResult::GetResult(
const extensions::CWSInfoService* extension_info_service,
Profile* profile,
bool only_unpublished_extensions = false) {
extensions::ExtensionPrefs* extension_prefs =
extensions::ExtensionPrefsFactory::GetForBrowserContext(profile);
extensions::ExtensionRegistry* extension_registry =
extensions::ExtensionRegistry::Get(profile);
std::set<extensions::ExtensionId> triggering_extensions;
if (base::FeatureList::IsEnabled(extensions::kCWSInfoService)) {
const extensions::ExtensionSet all_installed_extensions =
extension_registry->GenerateInstalledExtensionsSet();
for (const auto& extension : all_installed_extensions) {
// Check if the extension is installed by a policy.
if (extensions::Manifest::IsPolicyLocation(extension->location())) {
continue;
}
if (!ShouldExtensionBeReviewed(*extension.get(), extension_prefs,
extension_info_service,
only_unpublished_extensions)) {
continue;
}
triggering_extensions.insert(std::move(extension->id()));
}
}
return std::make_unique<SafetyHubExtensionsResult>(
triggering_extensions, only_unpublished_extensions);
}
std::unique_ptr<SafetyHubService::Result> SafetyHubExtensionsResult::Clone()
const {
return std::make_unique<SafetyHubExtensionsResult>(*this);
}
base::Value::Dict SafetyHubExtensionsResult::ToDictValue() const {
// Only results that contain extensions that have been unpublished for a long
// time should be serialized.
CHECK(is_unpublished_extensions_only_);
base::Value::Dict result = BaseToDictValue();
base::Value::List extensions_list;
for (const auto& triggering_extension : triggering_extensions_) {
extensions_list.Append(triggering_extension);
}
result.Set(safety_hub::kSafetyHubTriggeringExtensionIdsKey,
std::move(extensions_list));
return result;
}
bool SafetyHubExtensionsResult::IsTriggerForMenuNotification() const {
// Only results that have unpublished extensions can result in a menu
// notification.
return is_unpublished_extensions_only_ && !triggering_extensions_.empty();
}
unsigned int SafetyHubExtensionsResult::GetNumTriggeringExtensions() const {
return triggering_extensions_.size();
}
bool SafetyHubExtensionsResult::WarrantsNewMenuNotification(
const Result& previousResult) const {
const auto& previous =
static_cast<const SafetyHubExtensionsResult&>(previousResult);
// Only results that are for unpublished extensions can result in a menu
// notification.
if (!is_unpublished_extensions_only_ ||
!previous.is_unpublished_extensions_only_) {
return false;
}
return !base::ranges::includes(previous.triggering_extensions_,
triggering_extensions_);
}
std::u16string SafetyHubExtensionsResult::GetNotificationString() const {
CHECK(is_unpublished_extensions_only_);
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MENU_NOTIFICATION,
triggering_extensions_.size());
}
int SafetyHubExtensionsResult::GetNotificationCommandId() const {
CHECK(is_unpublished_extensions_only_);
return IDC_MANAGE_EXTENSIONS;
}