blob: e3ffcd71f75597f141e9fc3eedd94edd0ca6476c [file] [log] [blame]
// Copyright 2016 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/site_settings_helper.h"
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/json/values_util.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
#include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
#include "chrome/browser/hid/hid_chooser_context.h"
#include "chrome/browser/hid/hid_chooser_context_factory.h"
#include "chrome/browser/permissions/notification_permission_review_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/serial/serial_chooser_context.h"
#include "chrome/browser/serial/serial_chooser_context_factory.h"
#include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
#include "chrome/browser/ui/url_identity.h"
#include "chrome/browser/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.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/content_settings_utils.h"
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include "components/permissions/object_permission_context_base.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/permissions/permission_result.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/permissions_client.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h"
#include "components/subresource_filter/content/browser/subresource_filter_profile_context.h"
#include "components/subresource_filter/core/browser/subresource_filter_features.h"
#include "components/url_formatter/url_formatter.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_result.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_utils.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "services/network/public/cpp/features.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
namespace site_settings {
constexpr char kAppName[] = "appName";
constexpr char kAppId[] = "appId";
namespace {
// Chooser data group names.
const char kUsbChooserDataGroupType[] = "usb-devices-data";
const char kSerialChooserDataGroupType[] = "serial-ports-data";
const char kHidChooserDataGroupType[] = "hid-devices-data";
const char kBluetoothChooserDataGroupType[] = "bluetooth-devices-data";
const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = {
// The following ContentSettingsTypes have UI in Content Settings
// and require a mapping from their Javascript string representation in
// chrome/browser/resources/settings/site_settings/constants.ts to their C++
// ContentSettingsType provided here. These group names are only used by
// desktop webui.
{ContentSettingsType::COOKIES, "cookies"},
{ContentSettingsType::IMAGES, "images"},
{ContentSettingsType::JAVASCRIPT, "javascript"},
{ContentSettingsType::POPUPS, "popups"},
{ContentSettingsType::GEOLOCATION, "location"},
{ContentSettingsType::NOTIFICATIONS, "notifications"},
{ContentSettingsType::MEDIASTREAM_MIC, "media-stream-mic"},
{ContentSettingsType::MEDIASTREAM_CAMERA, "media-stream-camera"},
{ContentSettingsType::PROTOCOL_HANDLERS, "register-protocol-handler"},
{ContentSettingsType::AUTOMATIC_DOWNLOADS, "multiple-automatic-downloads"},
{ContentSettingsType::MIDI_SYSEX, "midi-sysex"},
{ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER, "protected-content"},
{ContentSettingsType::BACKGROUND_SYNC, "background-sync"},
{ContentSettingsType::ADS, "ads"},
{ContentSettingsType::SOUND, "sound"},
{ContentSettingsType::CLIPBOARD_READ_WRITE, "clipboard"},
{ContentSettingsType::SENSORS, "sensors"},
{ContentSettingsType::PAYMENT_HANDLER, "payment-handler"},
{ContentSettingsType::USB_GUARD, "usb-devices"},
{ContentSettingsType::USB_CHOOSER_DATA, kUsbChooserDataGroupType},
{ContentSettingsType::IDLE_DETECTION, "idle-detection"},
{ContentSettingsType::SERIAL_GUARD, "serial-ports"},
{ContentSettingsType::SERIAL_CHOOSER_DATA, kSerialChooserDataGroupType},
{ContentSettingsType::BLUETOOTH_SCANNING, "bluetooth-scanning"},
{ContentSettingsType::HID_GUARD, "hid-devices"},
{ContentSettingsType::HID_CHOOSER_DATA, kHidChooserDataGroupType},
{ContentSettingsType::FILE_SYSTEM_WRITE_GUARD, "file-system-write"},
{ContentSettingsType::MIXEDSCRIPT, "mixed-script"},
{ContentSettingsType::VR, "vr"},
{ContentSettingsType::AR, "ar"},
{ContentSettingsType::BLUETOOTH_GUARD, "bluetooth-devices"},
{ContentSettingsType::BLUETOOTH_CHOOSER_DATA,
kBluetoothChooserDataGroupType},
{ContentSettingsType::WINDOW_MANAGEMENT, "window-placement"},
{ContentSettingsType::LOCAL_FONTS, "local-fonts"},
{ContentSettingsType::FILE_SYSTEM_ACCESS_CHOOSER_DATA,
"file-system-access-handles-data"},
{ContentSettingsType::FEDERATED_IDENTITY_API, "federated-identity-api"},
{ContentSettingsType::PRIVATE_NETWORK_GUARD, "private-network-devices"},
{ContentSettingsType::PRIVATE_NETWORK_CHOOSER_DATA,
"private-network-devices-data"},
{ContentSettingsType::ANTI_ABUSE, "anti-abuse"},
{ContentSettingsType::STORAGE_ACCESS, "storage-access"},
// Add new content settings here if a corresponding Javascript string
// representation for it is not required, for example if the content setting
// is not used for desktop. Note some exceptions do have UI in Content
// Settings but do not require a separate string.
{ContentSettingsType::DEFAULT, nullptr},
{ContentSettingsType::AUTO_SELECT_CERTIFICATE, nullptr},
{ContentSettingsType::SSL_CERT_DECISIONS, nullptr},
{ContentSettingsType::APP_BANNER, nullptr},
{ContentSettingsType::SITE_ENGAGEMENT, nullptr},
{ContentSettingsType::DURABLE_STORAGE, nullptr},
{ContentSettingsType::AUTOPLAY, nullptr},
{ContentSettingsType::IMPORTANT_SITE_INFO, nullptr},
{ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA, nullptr},
{ContentSettingsType::ADS_DATA, nullptr},
{ContentSettingsType::MIDI, nullptr},
{ContentSettingsType::PASSWORD_PROTECTION, nullptr},
{ContentSettingsType::MEDIA_ENGAGEMENT, nullptr},
{ContentSettingsType::CLIENT_HINTS, nullptr},
{ContentSettingsType::ACCESSIBILITY_EVENTS, nullptr},
{ContentSettingsType::CLIPBOARD_SANITIZED_WRITE, nullptr},
{ContentSettingsType::BACKGROUND_FETCH, nullptr},
{ContentSettingsType::INTENT_PICKER_DISPLAY, nullptr},
{ContentSettingsType::PERIODIC_BACKGROUND_SYNC, nullptr},
{ContentSettingsType::WAKE_LOCK_SCREEN, nullptr},
{ContentSettingsType::WAKE_LOCK_SYSTEM, nullptr},
{ContentSettingsType::LEGACY_COOKIE_ACCESS, nullptr},
{ContentSettingsType::NFC, nullptr},
{ContentSettingsType::SAFE_BROWSING_URL_CHECK_DATA, nullptr},
{ContentSettingsType::FILE_SYSTEM_READ_GUARD, nullptr},
{ContentSettingsType::CAMERA_PAN_TILT_ZOOM, nullptr},
{ContentSettingsType::INSECURE_LOCAL_NETWORK, nullptr},
{ContentSettingsType::PERMISSION_AUTOREVOCATION_DATA, nullptr},
{ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, nullptr},
{ContentSettingsType::DISPLAY_CAPTURE, nullptr},
{ContentSettingsType::FEDERATED_IDENTITY_SHARING, nullptr},
{ContentSettingsType::JAVASCRIPT_JIT, nullptr},
{ContentSettingsType::HTTP_ALLOWED, nullptr},
{ContentSettingsType::HTTPS_ENFORCED, nullptr},
{ContentSettingsType::FORMFILL_METADATA, nullptr},
{ContentSettingsType::FEDERATED_IDENTITY_ACTIVE_SESSION, nullptr},
{ContentSettingsType::AUTO_DARK_WEB_CONTENT, nullptr},
{ContentSettingsType::REQUEST_DESKTOP_SITE, nullptr},
{ContentSettingsType::NOTIFICATION_INTERACTIONS, nullptr},
{ContentSettingsType::REDUCED_ACCEPT_LANGUAGE, nullptr},
{ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, nullptr},
{ContentSettingsType::FEDERATED_IDENTITY_IDENTITY_PROVIDER_SIGNIN_STATUS,
nullptr},
// PPAPI_BROKER has been deprecated. The content setting is not used or
// called from UI, so we don't need a representation JS string.
{ContentSettingsType::DEPRECATED_PPAPI_BROKER, nullptr},
{ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, nullptr},
{ContentSettingsType::TOP_LEVEL_STORAGE_ACCESS, nullptr},
// TODO(crbug.com/1408520): Update JavaScript string representation when
// desktop UI is implemented.
{ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION, nullptr},
{ContentSettingsType::FEDERATED_IDENTITY_IDENTITY_PROVIDER_REGISTRATION,
nullptr},
{ContentSettingsType::THIRD_PARTY_STORAGE_PARTITIONING, nullptr},
{ContentSettingsType::ALL_SCREEN_CAPTURE, nullptr},
};
static_assert(std::size(kContentSettingsTypeGroupNames) ==
// ContentSettingsType starts at -1, so add 1 here.
static_cast<int32_t>(ContentSettingsType::NUM_TYPES) + 1,
"kContentSettingsTypeGroupNames should have "
"CONTENT_SETTINGS_NUM_TYPES elements");
struct SiteSettingSourceStringMapping {
SiteSettingSource source;
const char* source_str;
};
const SiteSettingSourceStringMapping kSiteSettingSourceStringMapping[] = {
{SiteSettingSource::kAllowlist, "allowlist"},
{SiteSettingSource::kAdsFilterBlocklist, "ads-filter-blacklist"},
{SiteSettingSource::kDefault, "default"},
{SiteSettingSource::kEmbargo, "embargo"},
{SiteSettingSource::kExtension, "extension"},
{SiteSettingSource::kHostedApp, "HostedApp"},
{SiteSettingSource::kInsecureOrigin, "insecure-origin"},
{SiteSettingSource::kKillSwitch, "kill-switch"},
{SiteSettingSource::kPolicy, "policy"},
{SiteSettingSource::kPreference, "preference"},
};
static_assert(std::size(kSiteSettingSourceStringMapping) ==
static_cast<int>(SiteSettingSource::kNumSources),
"kSiteSettingSourceStringMapping should have "
"SiteSettingSource::kNumSources elements");
// Retrieves the corresponding string, according to the following precedence
// order from highest to lowest priority:
// 1. Allowlisted WebUI content setting.
// 2. Kill-switch.
// 3. Insecure origins (some permissions are denied to insecure origins).
// 4. Enterprise policy.
// 5. Extensions.
// 6. Activated for ads filtering (for Ads ContentSettingsType only).
// 7. User-set per-origin setting.
// 8. Embargo.
// 9. User-set patterns.
// 10. User-set global default for a ContentSettingsType.
// 11. Chrome's built-in default.
SiteSettingSource CalculateSiteSettingSource(
Profile* profile,
const ContentSettingsType content_type,
const GURL& origin,
const content_settings::SettingInfo& info,
const permissions::PermissionResult result) {
if (info.source == content_settings::SETTING_SOURCE_ALLOWLIST)
return SiteSettingSource::kAllowlist; // Source #1.
if (result.source == permissions::PermissionStatusSource::KILL_SWITCH)
return SiteSettingSource::kKillSwitch; // Source #2.
if (result.source == permissions::PermissionStatusSource::INSECURE_ORIGIN)
return SiteSettingSource::kInsecureOrigin; // Source #3.
if (info.source == content_settings::SETTING_SOURCE_POLICY ||
info.source == content_settings::SETTING_SOURCE_SUPERVISED) {
return SiteSettingSource::kPolicy; // Source #4.
}
if (info.source == content_settings::SETTING_SOURCE_EXTENSION)
return SiteSettingSource::kExtension; // Source #5.
if (content_type == ContentSettingsType::ADS &&
base::FeatureList::IsEnabled(
subresource_filter::kSafeBrowsingSubresourceFilter)) {
subresource_filter::SubresourceFilterContentSettingsManager*
settings_manager =
SubresourceFilterProfileContextFactory::GetForProfile(profile)
->settings_manager();
if (settings_manager->GetSiteActivationFromMetadata(origin)) {
return SiteSettingSource::kAdsFilterBlocklist; // Source #6.
}
}
DCHECK_NE(content_settings::SETTING_SOURCE_NONE, info.source);
if (info.source == content_settings::SETTING_SOURCE_USER) {
if (result.source ==
permissions::PermissionStatusSource::MULTIPLE_DISMISSALS ||
result.source ==
permissions::PermissionStatusSource::MULTIPLE_IGNORES) {
return SiteSettingSource::kEmbargo; // Source #8.
}
if (info.primary_pattern == ContentSettingsPattern::Wildcard() &&
info.secondary_pattern == ContentSettingsPattern::Wildcard()) {
return SiteSettingSource::kDefault; // Source #10, #11.
}
// Source #7, #9. When #7 is the source, |result.source| won't be set to
// any of the source #7 enum values, as PermissionManager is aware of the
// difference between these two sources internally. The subtlety here should
// go away when PermissionManager can handle all content settings and all
// possible sources.
return SiteSettingSource::kPreference;
}
NOTREACHED();
return SiteSettingSource::kPreference;
}
bool PatternAppliesToWebUISchemes(const ContentSettingPatternSource& pattern) {
return pattern.primary_pattern.GetScheme() ==
ContentSettingsPattern::SchemeType::SCHEME_CHROME ||
pattern.primary_pattern.GetScheme() ==
ContentSettingsPattern::SchemeType::SCHEME_CHROMEUNTRUSTED ||
pattern.primary_pattern.GetScheme() ==
ContentSettingsPattern::SchemeType::SCHEME_DEVTOOLS;
}
// If the given |pattern| represents an individual origin, Isolated Web App, or
// extension, retrieve a string to display it as such. If not, return the
// pattern as a string.
std::string GetDisplayNameForPattern(Profile* profile,
const ContentSettingsPattern& pattern) {
GURL url(pattern.ToString());
if (url.SchemeIs(extensions::kExtensionScheme) ||
url.SchemeIs(chrome::kIsolatedAppScheme)) {
return GetDisplayNameForGURL(profile, url, /*hostname_only=*/false);
}
return pattern.ToString();
}
// Returns exceptions constructed from the policy-set allowed URLs
// for the content settings |type| mic or camera.
void GetPolicyAllowedUrls(ContentSettingsType type,
std::vector<base::Value::Dict>* exceptions,
content::WebUI* web_ui,
bool incognito) {
DCHECK(type == ContentSettingsType::MEDIASTREAM_MIC ||
type == ContentSettingsType::MEDIASTREAM_CAMERA);
Profile* profile = Profile::FromWebUI(web_ui);
PrefService* prefs = profile->GetPrefs();
const base::Value::List& policy_urls =
prefs->GetList(type == ContentSettingsType::MEDIASTREAM_MIC
? prefs::kAudioCaptureAllowedUrls
: prefs::kVideoCaptureAllowedUrls);
// Convert the URLs to |ContentSettingsPattern|s. Ignore any invalid ones.
std::vector<ContentSettingsPattern> patterns;
for (const auto& entry : policy_urls) {
const std::string* url = entry.GetIfString();
if (!url) {
continue;
}
ContentSettingsPattern pattern = ContentSettingsPattern::FromString(*url);
if (!pattern.IsValid()) {
continue;
}
patterns.push_back(pattern);
}
// The patterns are shown in the UI in a reverse order defined by
// |ContentSettingsPattern::operator<|.
std::sort(patterns.begin(), patterns.end(),
std::greater<ContentSettingsPattern>());
for (const ContentSettingsPattern& pattern : patterns) {
std::string display_name = GetDisplayNameForPattern(profile, pattern);
exceptions->push_back(GetExceptionForPage(
type, profile, pattern, ContentSettingsPattern(), display_name,
CONTENT_SETTING_ALLOW,
SiteSettingSourceToString(SiteSettingSource::kPolicy), incognito));
}
}
// Retrieves the source of a chooser exception as a string. This method uses the
// CalculateSiteSettingSource method above to calculate the correct string to
// use.
std::string GetSourceStringForChooserException(
Profile* profile,
ContentSettingsType content_type,
content_settings::SettingSource source) {
// Prepare the parameters needed by CalculateSiteSettingSource
content_settings::SettingInfo info;
info.source = source;
// Chooser exceptions do not use a PermissionContextBase for their
// permissions.
permissions::PermissionResult permission_result(
CONTENT_SETTING_DEFAULT,
permissions::PermissionStatusSource::UNSPECIFIED);
// The |origin| parameter is only used for |ContentSettingsType::ADS| with
// the |kSafeBrowsingSubresourceFilter| feature flag enabled, so an empty GURL
// is used.
SiteSettingSource calculated_source = CalculateSiteSettingSource(
profile, content_type, /*origin=*/GURL::EmptyGURL(), info,
permission_result);
DCHECK(calculated_source == SiteSettingSource::kPolicy ||
calculated_source == SiteSettingSource::kPreference);
return SiteSettingSourceToString(calculated_source);
}
permissions::ObjectPermissionContextBase* GetUsbChooserContext(
Profile* profile) {
return UsbChooserContextFactory::GetForProfile(profile);
}
permissions::ObjectPermissionContextBase* GetSerialChooserContext(
Profile* profile) {
return SerialChooserContextFactory::GetForProfile(profile);
}
permissions::ObjectPermissionContextBase* GetHidChooserContext(
Profile* profile) {
return HidChooserContextFactory::GetForProfile(profile);
}
// The BluetoothChooserContext is only available when the
// WebBluetoothNewPermissionsBackend flag is enabled.
// TODO(https://crbug.com/589228): Remove the feature check when it is enabled
// by default.
permissions::ObjectPermissionContextBase* GetBluetoothChooserContext(
Profile* profile) {
if (base::FeatureList::IsEnabled(
features::kWebBluetoothNewPermissionsBackend)) {
return BluetoothChooserContextFactory::GetForProfile(profile);
}
return nullptr;
}
const ChooserTypeNameEntry kChooserTypeGroupNames[] = {
{&GetUsbChooserContext, kUsbChooserDataGroupType},
{&GetSerialChooserContext, kSerialChooserDataGroupType},
{&GetHidChooserContext, kHidChooserDataGroupType},
{&GetBluetoothChooserContext, kBluetoothChooserDataGroupType}};
// These variables represent different formatting options for default (i.e. not
// extension or IWA) URLs as well as fallbacks for when the IWA/extension is not
// found in the registry.
constexpr UrlIdentity::FormatOptions kUrlIdentityOptionsOmitHttps = {
.default_options = {
UrlIdentity::DefaultFormatOptions::kOmitCryptographicScheme}};
constexpr UrlIdentity::FormatOptions kUrlIdentityOptionsHostOnly = {
.default_options = {UrlIdentity::DefaultFormatOptions::kHostname}};
constexpr UrlIdentity::FormatOptions kUrlIdentityOptionsRawSpec = {
.default_options = {UrlIdentity::DefaultFormatOptions::kRawSpec}};
constexpr UrlIdentity::TypeSet kUrlIdentityAllowedTypes = {
UrlIdentity::Type::kDefault, UrlIdentity::Type::kFile,
UrlIdentity::Type::kIsolatedWebApp, UrlIdentity::Type::kChromeExtension};
bool ShouldAddToNotificationPermissionReviewList(
site_engagement::SiteEngagementService* service,
GURL url,
int notification_count) {
// The notification permission should be added to the list if one of the
// criteria below holds:
// - Site engagement level is NONE OR MINIMAL and average daily notification
// count is more than 0.
// - Site engamment level is LOW and average daily notification count is
// more than 3. Otherwise, the notification permission should not be added
// to review list.
double score = service->GetScore(url);
int low_engagement_notification_limit =
features::kSafetyCheckNotificationPermissionsLowEnagementLimit.Get();
bool is_low_engagement =
!site_engagement::SiteEngagementService::IsEngagementAtLeast(
score, blink::mojom::EngagementLevel::MEDIUM) &&
notification_count > low_engagement_notification_limit;
int min_engagement_notification_limit =
features::kSafetyCheckNotificationPermissionsMinEnagementLimit.Get();
bool is_minimal_engagement =
!site_engagement::SiteEngagementService::IsEngagementAtLeast(
score, blink::mojom::EngagementLevel::LOW) &&
notification_count > min_engagement_notification_limit;
return is_minimal_engagement || is_low_engagement;
}
} // namespace
bool HasRegisteredGroupName(ContentSettingsType type) {
for (size_t i = 0; i < std::size(kContentSettingsTypeGroupNames); ++i) {
if (type == kContentSettingsTypeGroupNames[i].type &&
kContentSettingsTypeGroupNames[i].name) {
return true;
}
}
return false;
}
ContentSettingsType ContentSettingsTypeFromGroupName(base::StringPiece name) {
for (const auto& entry : kContentSettingsTypeGroupNames) {
// Content setting types that aren't represented in the settings UI
// will have `nullptr` as their `name`. However, converting `nullptr`
// to a StringPiece will crash, so we have to handle it explicitly
// before comparing.
if (entry.name != nullptr && entry.name == name) {
return entry.type;
}
}
return ContentSettingsType::DEFAULT;
}
base::StringPiece ContentSettingsTypeToGroupName(ContentSettingsType type) {
for (size_t i = 0; i < std::size(kContentSettingsTypeGroupNames); ++i) {
if (type == kContentSettingsTypeGroupNames[i].type) {
const char* name = kContentSettingsTypeGroupNames[i].name;
if (name)
return name;
break;
}
}
NOTREACHED() << static_cast<int32_t>(type)
<< " is not a recognized content settings type.";
return base::StringPiece();
}
const std::vector<ContentSettingsType>& GetVisiblePermissionCategories() {
// First build the list of permissions that will be shown regardless of
// `origin`. Some categories such as COOKIES store their data in a custom way,
// so are not included here.
static base::NoDestructor<std::vector<ContentSettingsType>> base_types{{
ContentSettingsType::AR,
ContentSettingsType::AUTOMATIC_DOWNLOADS,
ContentSettingsType::BACKGROUND_SYNC,
ContentSettingsType::CLIPBOARD_READ_WRITE,
ContentSettingsType::FILE_SYSTEM_WRITE_GUARD,
ContentSettingsType::GEOLOCATION,
ContentSettingsType::HID_GUARD,
ContentSettingsType::IDLE_DETECTION,
ContentSettingsType::IMAGES,
ContentSettingsType::JAVASCRIPT,
ContentSettingsType::LOCAL_FONTS,
ContentSettingsType::MEDIASTREAM_CAMERA,
ContentSettingsType::MEDIASTREAM_MIC,
ContentSettingsType::MIDI_SYSEX,
ContentSettingsType::MIXEDSCRIPT,
ContentSettingsType::NOTIFICATIONS,
ContentSettingsType::POPUPS,
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
#endif
ContentSettingsType::SENSORS,
ContentSettingsType::SERIAL_GUARD,
ContentSettingsType::SOUND,
ContentSettingsType::USB_GUARD,
ContentSettingsType::VR,
ContentSettingsType::WINDOW_MANAGEMENT,
}};
static bool initialized = false;
if (!initialized) {
// The permission categories in this block are only shown when running with
// certain flags/switches.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kEnableExperimentalWebPlatformFeatures)) {
base_types->push_back(ContentSettingsType::BLUETOOTH_SCANNING);
}
if (base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps))
base_types->push_back(ContentSettingsType::PAYMENT_HANDLER);
if (base::FeatureList::IsEnabled(features::kFedCm)) {
base_types->push_back(ContentSettingsType::FEDERATED_IDENTITY_API);
}
if (base::FeatureList::IsEnabled(
features::kWebBluetoothNewPermissionsBackend)) {
base_types->push_back(ContentSettingsType::BLUETOOTH_GUARD);
}
if (base::FeatureList::IsEnabled(
subresource_filter::kSafeBrowsingSubresourceFilter)) {
base_types->push_back(ContentSettingsType::ADS);
}
if (base::FeatureList::IsEnabled(
network::features::kPrivateNetworkAccessPermissionPrompt)) {
base_types->push_back(ContentSettingsType::PRIVATE_NETWORK_GUARD);
}
initialized = true;
}
return *base_types;
}
std::string SiteSettingSourceToString(const SiteSettingSource source) {
return kSiteSettingSourceStringMapping[static_cast<int>(source)].source_str;
}
// Add an "Allow"-entry to the list of |exceptions| for a |url_pattern| from
// the web extent of a hosted |app|.
void AddExceptionForHostedApp(const std::string& url_pattern,
const extensions::Extension& app,
base::Value::List* exceptions) {
base::Value::Dict exception;
std::string setting_string =
content_settings::ContentSettingToString(CONTENT_SETTING_ALLOW);
DCHECK(!setting_string.empty());
exception.Set(kSetting, setting_string);
exception.Set(kOrigin, url_pattern);
exception.Set(kDisplayName, url_pattern);
exception.Set(kEmbeddingOrigin, url_pattern);
exception.Set(kSource,
SiteSettingSourceToString(SiteSettingSource::kHostedApp));
exception.Set(kIncognito, false);
exception.Set(kAppName, app.name());
exception.Set(kAppId, app.id());
exceptions->Append(std::move(exception));
}
// Create a base::Value::Dict that will act as a data source for a single row
// for a File System Access permission grant.
base::Value::Dict GetFileSystemExceptionForPage(
ContentSettingsType content_type,
Profile* profile,
const std::string& origin,
const base::FilePath& file_path,
const ContentSetting& setting,
const std::string& provider_name,
bool incognito,
bool is_embargoed) {
base::Value::Dict exception;
exception.Set(kOrigin, origin);
// TODO(crbug.com/1373962): Replace `LossyDisplayName` method with a
// new method that returns the full file path in a human-readable format.
exception.Set(kDisplayName, file_path.LossyDisplayName());
std::string setting_string =
content_settings::ContentSettingToString(setting);
DCHECK(!setting_string.empty());
exception.Set(kSetting, setting_string);
exception.Set(kSource, provider_name);
exception.Set(kIncognito, incognito);
exception.Set(kIsEmbargoed, is_embargoed);
return exception;
}
// Create a base::Value::Dict that will act as a data source for a single row
// in a HostContentSettingsMap-controlled exceptions table (e.g., cookies).
base::Value::Dict GetExceptionForPage(
ContentSettingsType content_type,
Profile* profile,
const ContentSettingsPattern& pattern,
const ContentSettingsPattern& secondary_pattern,
const std::string& display_name,
const ContentSetting& setting,
const std::string& provider_name,
bool incognito,
bool is_embargoed) {
base::Value::Dict exception;
exception.Set(kOrigin, pattern.ToString());
exception.Set(kDisplayName, display_name);
exception.Set(kEmbeddingOrigin,
secondary_pattern == ContentSettingsPattern::Wildcard()
? std::string()
: secondary_pattern.ToString());
std::string setting_string =
content_settings::ContentSettingToString(setting);
DCHECK(!setting_string.empty());
exception.Set(kSetting, setting_string);
exception.Set(kSource, provider_name);
exception.Set(kIncognito, incognito);
exception.Set(kIsEmbargoed, is_embargoed);
return exception;
}
UrlIdentity GetUrlIdentityForGURL(Profile* profile,
const GURL& url,
bool hostname_only) {
auto origin = url::Origin::Create(url);
if (origin.opaque()) {
return {.type = UrlIdentity::Type::kDefault,
.name = base::UTF8ToUTF16(url.spec())};
}
return UrlIdentity::CreateFromUrl(
profile, origin.GetURL(), kUrlIdentityAllowedTypes,
hostname_only ? kUrlIdentityOptionsHostOnly
: kUrlIdentityOptionsOmitHttps);
}
std::string GetDisplayNameForGURL(Profile* profile,
const GURL& url,
bool hostname_only) {
return base::UTF16ToUTF8(
GetUrlIdentityForGURL(profile, url, hostname_only).name);
}
void GetExceptionsForContentType(ContentSettingsType type,
Profile* profile,
content::WebUI* web_ui,
bool incognito,
base::Value::List* exceptions) {
ContentSettingsForOneType all_settings;
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile);
map->GetSettingsForOneType(type, &all_settings);
// Group settings by primary_pattern.
AllPatternsSettings all_patterns_settings;
for (const auto& setting : all_settings) {
// Don't add default settings.
if (setting.primary_pattern == ContentSettingsPattern::Wildcard() &&
setting.secondary_pattern == ContentSettingsPattern::Wildcard() &&
setting.source !=
SiteSettingSourceToString(SiteSettingSource::kPreference)) {
continue;
}
// Off-the-record HostContentSettingsMap contains incognito content settings
// as well as normal content settings. Here, we use the incognito settings
// only.
if (map->IsOffTheRecord() && !setting.incognito)
continue;
// Don't add WebUI settings.
if (PatternAppliesToWebUISchemes(setting)) {
continue;
}
auto content_setting = setting.GetContentSetting();
if (type == ContentSettingsType::COOKIES &&
base::FeatureList::IsEnabled(
privacy_sandbox::kPrivacySandboxSettings4)) {
// With the changes to settings introduced in PrivacySandboxSettings4,
// there is no user-facing concept of SESSION_ONLY cookie exceptions that
// use secondary patterns. These are instead presented as ALLOW.
// TODO(crbug.com/1404436): Perform a one time migration of the actual
// content settings when the extension API no-longer allows them to be
// created.
if (content_setting == ContentSetting::CONTENT_SETTING_SESSION_ONLY &&
setting.secondary_pattern != ContentSettingsPattern::Wildcard()) {
content_setting = ContentSetting::CONTENT_SETTING_ALLOW;
}
}
all_patterns_settings[std::make_pair(
setting.primary_pattern, setting.source)][setting.secondary_pattern] =
content_setting;
}
ContentSettingsForOneType embargo_settings;
map->GetSettingsForOneType(ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA,
&embargo_settings);
permissions::PermissionDecisionAutoBlocker* auto_blocker =
permissions::PermissionsClient::Get()->GetPermissionDecisionAutoBlocker(
profile);
std::set<ContentSettingsPattern> origins_under_embargo;
for (const auto& setting : embargo_settings) {
// Off-the-record HostContentSettingsMap contains incognito content
// settings as well as normal content settings. Here, we use the
// incognito settings only.
if (map->IsOffTheRecord() && !setting.incognito)
continue;
if (!permissions::PermissionDecisionAutoBlocker::IsEnabledForContentSetting(
type)) {
continue;
}
if (auto_blocker->IsEmbargoed(GURL(setting.primary_pattern.ToString()),
type)) {
origins_under_embargo.insert(setting.primary_pattern);
all_patterns_settings[std::make_pair(
setting.primary_pattern, setting.source)][setting.secondary_pattern] =
CONTENT_SETTING_BLOCK;
}
}
// Keep the exceptions sorted by provider so they will be displayed in
// precedence order.
std::vector<base::Value::Dict>
all_provider_exceptions[HostContentSettingsMap::NUM_PROVIDER_TYPES];
// |all_patterns_settings| is sorted from the lowest precedence pattern to
// the highest (see operator< in ContentSettingsPattern), so traverse it in
// reverse to show the patterns with the highest precedence (the more specific
// ones) on the top.
for (const auto& [primary_pattern_and_source, one_settings] :
base::Reversed(all_patterns_settings)) {
const auto& [primary_pattern, source] = primary_pattern_and_source;
const std::string display_name =
GetDisplayNameForPattern(profile, primary_pattern);
auto& this_provider_exceptions = all_provider_exceptions
[HostContentSettingsMap::GetProviderTypeFromSource(source)];
for (auto j = one_settings.begin(); j != one_settings.end(); ++j) {
ContentSetting content_setting = j->second;
this_provider_exceptions.push_back(GetExceptionForPage(
type, profile, primary_pattern, j->first, display_name,
content_setting, source, incognito,
base::Contains(origins_under_embargo, primary_pattern)));
}
}
// For camera and microphone, we do not have policy exceptions, but we do have
// the policy-set allowed URLs, which should be displayed in the same manner.
if (type == ContentSettingsType::MEDIASTREAM_MIC ||
type == ContentSettingsType::MEDIASTREAM_CAMERA) {
auto& policy_exceptions = all_provider_exceptions
[HostContentSettingsMap::GetProviderTypeFromSource(
SiteSettingSourceToString(SiteSettingSource::kPolicy))];
DCHECK(policy_exceptions.empty());
GetPolicyAllowedUrls(type, &policy_exceptions, web_ui, incognito);
}
// Display the URLs with File System entries that are granted
// permissions via File System Access Persistent Permissions.
if (base::FeatureList::IsEnabled(
features::kFileSystemAccessPersistentPermissions) &&
(type == ContentSettingsType::FILE_SYSTEM_READ_GUARD ||
type == ContentSettingsType::FILE_SYSTEM_WRITE_GUARD)) {
auto& urls_with_granted_entries = all_provider_exceptions
[HostContentSettingsMap::GetProviderTypeFromSource(
SiteSettingSourceToString(SiteSettingSource::kDefault))];
GetFileSystemGrantedEntries(&urls_with_granted_entries, profile, incognito);
}
for (auto& one_provider_exceptions : all_provider_exceptions) {
for (auto& exception : one_provider_exceptions)
exceptions->Append(std::move(exception));
}
}
void GetContentCategorySetting(const HostContentSettingsMap* map,
ContentSettingsType content_type,
base::Value::Dict* object) {
std::string provider;
std::string setting = content_settings::ContentSettingToString(
map->GetDefaultContentSetting(content_type, &provider));
DCHECK(!setting.empty());
object->Set(kSetting, setting);
if (provider != SiteSettingSourceToString(SiteSettingSource::kDefault))
object->Set(kSource, provider);
}
ContentSetting GetContentSettingForOrigin(Profile* profile,
const HostContentSettingsMap* map,
const GURL& origin,
ContentSettingsType content_type,
std::string* source_string) {
// TODO(patricialor): In future, PermissionManager should know about all
// content settings, not just the permissions, plus all the possible sources,
// and the calls to HostContentSettingsMap should be removed.
content_settings::SettingInfo info;
const base::Value value =
map->GetWebsiteSetting(origin, origin, content_type, &info);
// Retrieve the content setting.
permissions::PermissionResult result(
content_settings::ValueToContentSetting(value),
permissions::PermissionStatusSource::UNSPECIFIED);
if (permissions::PermissionDecisionAutoBlocker::IsEnabledForContentSetting(
content_type)) {
if (permissions::PermissionUtil::IsPermission(content_type)) {
content::PermissionResult permission_result =
profile->GetPermissionController()
->GetPermissionResultForOriginWithoutContext(
permissions::PermissionUtil::
ContentSettingTypeToPermissionType(content_type),
url::Origin::Create(origin));
result =
permissions::PermissionUtil::ToPermissionResult(permission_result);
} else {
permissions::PermissionDecisionAutoBlocker* auto_blocker =
permissions::PermissionsClient::Get()
->GetPermissionDecisionAutoBlocker(profile);
absl::optional<permissions::PermissionResult> embargo_result =
auto_blocker->GetEmbargoResult(origin, content_type);
if (embargo_result)
result = *embargo_result;
}
}
// Retrieve the source of the content setting.
*source_string = SiteSettingSourceToString(
CalculateSiteSettingSource(profile, content_type, origin, info, result));
if (info.metadata.session_model() ==
content_settings::SessionModel::OneTime) {
DCHECK(
permissions::PermissionUtil::CanPermissionBeAllowedOnce(content_type));
DCHECK_EQ(result.content_setting, CONTENT_SETTING_ALLOW);
return CONTENT_SETTING_DEFAULT;
}
return result.content_setting;
}
std::vector<ContentSettingPatternSource>
GetSingleOriginExceptionsForContentType(HostContentSettingsMap* map,
ContentSettingsType content_type) {
ContentSettingsForOneType entries;
map->GetSettingsForOneType(content_type, &entries);
// Exclude any entries that don't represent a single webby top-frame origin.
base::EraseIf(entries, [](const ContentSettingPatternSource& e) {
return !content_settings::PatternAppliesToSingleOrigin(
e.primary_pattern, e.secondary_pattern) ||
PatternAppliesToWebUISchemes(e);
});
return entries;
}
void GetFileSystemGrantedEntries(std::vector<base::Value::Dict>* exceptions,
Profile* profile,
bool incognito) {
ChromeFileSystemAccessPermissionContext* permission_context =
FileSystemAccessPermissionContextFactory::GetForProfile(profile);
std::vector<std::unique_ptr<permissions::ObjectPermissionContextBase::Object>>
grants = permission_context->GetAllGrantedObjects();
for (const auto& grant : grants) {
const std::string url = grant->origin.spec();
auto* const optional_path = grant->value.Find(
ChromeFileSystemAccessPermissionContext::kPermissionPathKey);
// Ensure that the file path is found for the given kPermissionPathKey.
if (optional_path) {
const base::FilePath file_path =
base::ValueToFilePath(optional_path).value();
exceptions->push_back(GetFileSystemExceptionForPage(
ContentSettingsType::FILE_SYSTEM_WRITE_GUARD, profile, url, file_path,
CONTENT_SETTING_ALLOW,
SiteSettingSourceToString(SiteSettingSource::kDefault), incognito));
}
}
// Sort exceptions by origin name, alphabetically.
base::ranges::sort(*exceptions, [](const base::Value::Dict& lhs,
const base::Value::Dict& rhs) {
return lhs.Find(kOrigin)->GetString() < rhs.Find(kOrigin)->GetString();
});
}
const ChooserTypeNameEntry* ChooserTypeFromGroupName(base::StringPiece name) {
for (const auto& chooser_type : kChooserTypeGroupNames) {
if (chooser_type.name == name)
return &chooser_type;
}
return nullptr;
}
// Create a base::Value::Dict that will act as a data source for a single row
// in a chooser permission exceptions table. The chooser permission will contain
// a list of site exceptions that correspond to the exception.
base::Value::Dict CreateChooserExceptionObject(
const std::u16string& display_name,
const base::Value& object,
const std::string& chooser_type,
const ChooserExceptionDetails& chooser_exception_details,
Profile* profile) {
base::Value::Dict exception;
std::string setting_string =
content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT);
DCHECK(!setting_string.empty());
exception.Set(kDisplayName, display_name);
exception.Set(kObject, object.Clone());
exception.Set(kChooserType, chooser_type);
// Order the sites by the provider precedence order.
std::vector<base::Value::Dict>
all_provider_sites[HostContentSettingsMap::NUM_PROVIDER_TYPES];
for (const auto& details : chooser_exception_details) {
const GURL& origin = std::get<0>(details);
const std::string& source = std::get<1>(details);
const bool incognito = std::get<2>(details);
std::string site_display_name = base::UTF16ToUTF8(
UrlIdentity::CreateFromUrl(profile, origin, kUrlIdentityAllowedTypes,
kUrlIdentityOptionsRawSpec)
.name);
auto& this_provider_sites =
all_provider_sites[HostContentSettingsMap::GetProviderTypeFromSource(
source)];
base::Value::Dict site;
site.Set(kOrigin, origin.spec());
site.Set(kDisplayName, site_display_name);
site.Set(kSetting, setting_string);
site.Set(kSource, source);
site.Set(kIncognito, incognito);
this_provider_sites.push_back(std::move(site));
}
base::Value::List sites;
for (auto& one_provider_sites : all_provider_sites) {
for (auto& site : one_provider_sites) {
sites.Append(std::move(site));
}
}
exception.Set(kSites, std::move(sites));
return exception;
}
base::Value::List GetChooserExceptionListFromProfile(
Profile* profile,
const ChooserTypeNameEntry& chooser_type) {
base::Value::List exceptions;
ContentSettingsType content_type =
ContentSettingsTypeFromGroupName(std::string(chooser_type.name));
DCHECK(content_type != ContentSettingsType::DEFAULT);
// The BluetoothChooserContext is only available when the
// WebBluetoothNewPermissionsBackend flag is enabled.
// TODO(https://crbug.com/589228): Remove the nullptr check when it is enabled
// by default.
permissions::ObjectPermissionContextBase* chooser_context =
chooser_type.get_context(profile);
if (!chooser_context)
return exceptions;
std::vector<std::unique_ptr<permissions::ObjectPermissionContextBase::Object>>
objects = chooser_context->GetAllGrantedObjects();
if (profile->HasPrimaryOTRProfile()) {
Profile* incognito_profile =
profile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
permissions::ObjectPermissionContextBase* incognito_chooser_context =
chooser_type.get_context(incognito_profile);
std::vector<
std::unique_ptr<permissions::ObjectPermissionContextBase::Object>>
incognito_objects = incognito_chooser_context->GetAllGrantedObjects();
objects.insert(objects.end(),
std::make_move_iterator(incognito_objects.begin()),
std::make_move_iterator(incognito_objects.end()));
}
// Maps from a chooser exception name/object pair to a
// ChooserExceptionDetails. This will group and sort the exceptions by the UI
// string and object for display.
std::map<std::pair<std::u16string, base::Value>, ChooserExceptionDetails>
all_chooser_objects;
for (const auto& object : objects) {
// Don't include WebUI settings.
if (content::HasWebUIScheme(object->origin))
continue;
std::u16string name = chooser_context->GetObjectDisplayName(object->value);
auto& chooser_exception_details = all_chooser_objects[std::make_pair(
name, base::Value(object->value.Clone()))];
std::string source = GetSourceStringForChooserException(
profile, content_type, object->source);
chooser_exception_details.insert(
{object->origin, source, object->incognito});
}
for (const auto& all_chooser_objects_entry : all_chooser_objects) {
const std::u16string& name = all_chooser_objects_entry.first.first;
const base::Value& object = all_chooser_objects_entry.first.second;
const ChooserExceptionDetails& chooser_exception_details =
all_chooser_objects_entry.second;
exceptions.Append(CreateChooserExceptionObject(
name, object, chooser_type.name, chooser_exception_details, profile));
}
return exceptions;
}
std::vector<web_app::IsolatedWebAppUrlInfo> GetInstalledIsolatedWebApps(
Profile* profile) {
auto* web_app_provider = web_app::WebAppProvider::GetForWebApps(profile);
if (!web_app_provider) {
return {};
}
std::vector<web_app::IsolatedWebAppUrlInfo> iwas;
web_app::WebAppRegistrar& registrar = web_app_provider->registrar_unsafe();
for (const web_app::WebApp& web_app : registrar.GetApps()) {
if (!registrar.IsIsolated(web_app.app_id())) {
continue;
}
base::expected<web_app::IsolatedWebAppUrlInfo, std::string> url_info =
web_app::IsolatedWebAppUrlInfo::Create(web_app.scope());
if (url_info.has_value()) {
iwas.push_back(*url_info);
}
}
return iwas;
}
// TODO(crbug.com/1444024): Migrate to NotificationPermissionsReviewService.
base::Value::List PopulateNotificationPermissionReviewData(Profile* profile) {
base::Value::List result;
if (!base::FeatureList::IsEnabled(
features::kSafetyCheckNotificationPermissions)) {
return result;
}
auto* service =
NotificationPermissionsReviewServiceFactory::GetForProfile(profile);
if (!service) {
return result;
}
auto notification_permissions = service->GetNotificationSiteListForReview();
site_engagement::SiteEngagementService* engagement_service =
site_engagement::SiteEngagementService::Get(profile);
// Sort notification permissions by their priority for surfacing to the user.
auto notification_permission_ordering =
[](const permissions::NotificationPermissions& left,
const permissions::NotificationPermissions& right) {
return left.notification_count > right.notification_count;
};
std::sort(notification_permissions.begin(), notification_permissions.end(),
notification_permission_ordering);
for (const auto& notification_permission : notification_permissions) {
// Converting primary pattern to GURL should always be valid, since
// Notification Permission Review list only contains single origins. Those
// are filtered in
// NotificationPermissionsReviewService::GetNotificationSiteListForReview.
GURL url = GURL(notification_permission.primary_pattern.ToString());
DCHECK(url.is_valid());
if (!ShouldAddToNotificationPermissionReviewList(
engagement_service, url,
notification_permission.notification_count)) {
continue;
}
base::Value::Dict permission;
permission.Set(site_settings::kOrigin,
notification_permission.primary_pattern.ToString());
std::string notification_info_string =
base::UTF16ToUTF8(l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_REVIEW_NOTIFICATION_PERMISSIONS_COUNT_LABEL,
notification_permission.notification_count));
permission.Set(site_settings::kNotificationInfoString,
notification_info_string);
result.Append(std::move(permission));
}
return result;
}
} // namespace site_settings