blob: 52a940c2ee0eec03c6f066f924b9a61fb4fe103f [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 <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/values.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/notification_permission_review_service_factory.h"
#include "chrome/browser/permissions/unused_site_permissions_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/settings/site_settings_helper.h"
#include "chrome/common/chrome_features.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/permissions/constants.h"
#include "components/permissions/unused_site_permissions_service.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
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";
// Get values from |UnusedSitePermission| object in
// safety_hub_browser_proxy.ts.
std::tuple<url::Origin,
std::set<ContentSettingsType>,
content_settings::ContentSettingConstraints>
GetUnusedSitePermissionsFromDict(
const base::Value::Dict& unused_site_permissions) {
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());
const url::Origin origin = url::Origin::Create(url);
const base::Value::List* permissions =
unused_site_permissions.FindList(site_settings::kPermissions);
CHECK(permissions);
std::set<ContentSettingsType> permission_types;
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.";
permission_types.insert(type);
}
const base::Value* js_expiration =
unused_site_permissions.Find(kExpirationKey);
CHECK(js_expiration);
auto expiration = base::ValueToTime(js_expiration);
content_settings::ContentSettingConstraints constraints;
// TODO(https://crbug.com/1450356): we should store the lifetime of the
// permission, rather than just its expiration.
constraints.set_lifetime(constraints.DeltaFromCreationTime(*expiration));
return std::make_tuple(origin, permission_types, constraints);
}
} // namespace
SafetyHubHandler::SafetyHubHandler(Profile* profile)
: profile_(profile), clock_(base::DefaultClock::GetInstance()) {}
SafetyHubHandler::~SafetyHubHandler() = 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();
permissions::UnusedSitePermissionsService* service =
UnusedSitePermissionsServiceFactory::GetForProfile(profile_);
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());
auto [origin, permissions, constraints] =
GetUnusedSitePermissionsFromDict(args[0].GetDict());
permissions::UnusedSitePermissionsService* service =
UnusedSitePermissionsServiceFactory::GetForProfile(profile_);
service->UndoRegrantPermissionsForOrigin(permissions, constraints, origin);
SendUnusedSitePermissionsReviewList();
}
void SafetyHubHandler::HandleAcknowledgeRevokedUnusedSitePermissionsList(
const base::Value::List& args) {
permissions::UnusedSitePermissionsService* service =
UnusedSitePermissionsServiceFactory::GetForProfile(profile_);
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();
permissions::UnusedSitePermissionsService* service =
UnusedSitePermissionsServiceFactory::GetForProfile(profile_);
for (const auto& unused_site_permissions_js : unused_site_permissions_list) {
CHECK(unused_site_permissions_js.is_dict());
auto [origin, permissions, constraints] =
GetUnusedSitePermissionsFromDict(unused_site_permissions_js.GetDict());
service->StorePermissionInRevokedPermissionSetting(permissions, constraints,
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_);
ContentSettingsForOneType settings;
hcsm->GetSettingsForOneType(
ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, &settings);
for (const auto& revoked_permissions : settings) {
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) {
permissions_value_list.Append(
site_settings::ContentSettingsTypeToGroupName(
static_cast<ContentSettingsType>(type.GetInt())));
}
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));
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];
base::Value::List result =
site_settings::PopulateNotificationPermissionReviewData(profile_);
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();
auto* service =
NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
DCHECK(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::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();
auto* service =
NotificationPermissionsReviewServiceFactory::GetForProfile(profile_);
DCHECK(service);
for (const auto& origin : origins) {
const ContentSettingsPattern& primary_pattern =
ContentSettingsPattern::FromString(origin.GetString());
service->RemovePatternFromNotificationPermissionReviewBlocklist(
primary_pattern, ContentSettingsPattern::Wildcard());
}
SendNotificationPermissionReviewList();
}
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(
"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)));
}
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() {
// Notify observers that the permission review list could have changed. Note
// that the list is not guaranteed to have changed.
FireWebUIListener(
site_settings::kNotificationPermissionsReviewListMaybeChangedEvent,
site_settings::PopulateNotificationPermissionReviewData(profile_));
}
void SafetyHubHandler::SetClockForTesting(base::Clock* clock) {
clock_ = clock;
}
void SafetyHubHandler::OnJavascriptAllowed() {}
void SafetyHubHandler::OnJavascriptDisallowed() {}