blob: 3f151dfaa0aa2b5e8d50f0ec483d8fe1d98564bd [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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/no_destructor.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.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/hid/hid_chooser_context.h"
#include "chrome/browser/hid/hid_chooser_context_factory.h"
#include "chrome/browser/permissions/permission_manager_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/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/pref_names.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_manager.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/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/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 "url/origin.h"
namespace site_settings {
constexpr char kAppName[] = "appName";
constexpr char kAppId[] = "appId";
namespace {
// Maps from the UI string to the object it represents (for sorting purposes).
typedef std::multimap<std::string, const base::DictionaryValue*> SortedObjects;
// Maps from a secondary URL to the set of objects it has permission to access.
typedef std::map<GURL, SortedObjects> OneOriginObjects;
// Maps from a primary URL/source pair to a OneOriginObjects. All the mappings
// in OneOriginObjects share the given primary URL and source.
typedef std::map<std::pair<GURL, std::string>, OneOriginObjects>
AllOriginObjects;
// 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.js 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::PPAPI_BROKER, "ppapi-broker"},
{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_PLACEMENT, "window-placement"},
{ContentSettingsType::FONT_ACCESS, "font-access"},
{ContentSettingsType::FILE_SYSTEM_ACCESS_CHOOSER_DATA,
"file-system-access-handles-data"},
// 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::INSTALLED_WEB_APP_METADATA, nullptr},
{ContentSettingsType::NFC, nullptr},
{ContentSettingsType::SAFE_BROWSING_URL_CHECK_DATA, nullptr},
{ContentSettingsType::FILE_SYSTEM_READ_GUARD, nullptr},
{ContentSettingsType::STORAGE_ACCESS, nullptr},
{ContentSettingsType::CAMERA_PAN_TILT_ZOOM, nullptr},
{ContentSettingsType::INSECURE_PRIVATE_NETWORK, nullptr},
{ContentSettingsType::PERMISSION_AUTOREVOCATION_DATA, nullptr},
{ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, nullptr},
{ContentSettingsType::DISPLAY_CAPTURE, nullptr},
{ContentSettingsType::FEDERATED_IDENTITY_SHARING, nullptr},
{ContentSettingsType::FEDERATED_IDENTITY_REQUEST, nullptr},
{ContentSettingsType::JAVASCRIPT_JIT, nullptr},
{ContentSettingsType::HTTP_ALLOWED, nullptr},
{ContentSettingsType::FORMFILL_METADATA, nullptr},
{ContentSettingsType::FEDERATED_IDENTITY_ACTIVE_SESSION, nullptr},
{ContentSettingsType::AUTO_DARK_WEB_CONTENT, nullptr},
{ContentSettingsType::REQUEST_DESKTOP_SITE, nullptr},
};
static_assert(base::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(base::size(kSiteSettingSourceStringMapping) ==
static_cast<int>(SiteSettingSource::kNumSources),
"kSiteSettingSourceStringMapping should have "
"SiteSettingSource::kNumSources elements");
struct PolicyIndicatorTypeStringMapping {
PolicyIndicatorType source;
const char* indicator_str;
};
// Converts a policy indicator type to its JS usable string representation.
const PolicyIndicatorTypeStringMapping kPolicyIndicatorTypeStringMapping[] = {
{PolicyIndicatorType::kDevicePolicy, "devicePolicy"},
{PolicyIndicatorType::kExtension, "extension"},
{PolicyIndicatorType::kNone, "none"},
{PolicyIndicatorType::kOwner, "owner"},
{PolicyIndicatorType::kPrimaryUser, "primary_user"},
{PolicyIndicatorType::kRecommended, "recommended"},
{PolicyIndicatorType::kUserPolicy, "userPolicy"},
{PolicyIndicatorType::kParent, "parent"},
{PolicyIndicatorType::kChildRestriction, "childRestriction"},
};
static_assert(base::size(kPolicyIndicatorTypeStringMapping) ==
static_cast<int>(PolicyIndicatorType::kNumIndicators),
"kPolicyIndicatorStringMapping should have "
"PolicyIndicatorType::kNumIndicators 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;
}
// Whether |pattern| applies to a single origin.
bool PatternAppliesToSingleOrigin(const ContentSettingPatternSource& pattern) {
const GURL url(pattern.primary_pattern.ToString());
// Default settings and other patterns apply to multiple origins.
if (url::Origin::Create(url).opaque())
return false;
// Embedded content settings only match when |url| is embedded in another
// origin, so ignore non-wildcard secondary patterns.
if (pattern.secondary_pattern != ContentSettingsPattern::Wildcard()) {
return false;
}
return true;
}
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;
}
// 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}};
} // namespace
bool HasRegisteredGroupName(ContentSettingsType type) {
for (size_t i = 0; i < base::size(kContentSettingsTypeGroupNames); ++i) {
if (type == kContentSettingsTypeGroupNames[i].type &&
kContentSettingsTypeGroupNames[i].name) {
return true;
}
}
return false;
}
ContentSettingsType ContentSettingsTypeFromGroupName(base::StringPiece name) {
for (size_t i = 0; i < base::size(kContentSettingsTypeGroupNames); ++i) {
if (name == kContentSettingsTypeGroupNames[i].name)
return kContentSettingsTypeGroupNames[i].type;
}
NOTREACHED() << name << " is not a recognized content settings type.";
return ContentSettingsType::DEFAULT;
}
base::StringPiece ContentSettingsTypeToGroupName(ContentSettingsType type) {
for (size_t i = 0; i < base::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::FONT_ACCESS,
ContentSettingsType::GEOLOCATION,
ContentSettingsType::HID_GUARD,
ContentSettingsType::IDLE_DETECTION,
ContentSettingsType::IMAGES,
ContentSettingsType::JAVASCRIPT,
ContentSettingsType::MEDIASTREAM_CAMERA,
ContentSettingsType::MEDIASTREAM_MIC,
ContentSettingsType::MIDI_SYSEX,
ContentSettingsType::MIXEDSCRIPT,
ContentSettingsType::NOTIFICATIONS,
ContentSettingsType::POPUPS,
#if defined(IS_CHROMEOS_ASH) || BUILDFLAG(IS_WIN)
ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
#endif
ContentSettingsType::SENSORS,
ContentSettingsType::SERIAL_GUARD,
ContentSettingsType::SOUND,
ContentSettingsType::USB_GUARD,
ContentSettingsType::VR,
ContentSettingsType::WINDOW_PLACEMENT,
}};
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::kWebBluetoothNewPermissionsBackend)) {
base_types->push_back(ContentSettingsType::BLUETOOTH_GUARD);
}
if (base::FeatureList::IsEnabled(
subresource_filter::kSafeBrowsingSubresourceFilter)) {
base_types->push_back(ContentSettingsType::ADS);
}
initialized = true;
}
return *base_types;
}
std::string SiteSettingSourceToString(const SiteSettingSource source) {
return kSiteSettingSourceStringMapping[static_cast<int>(source)].source_str;
}
base::Value GetValueForManagedState(const site_settings::ManagedState& state) {
base::Value value(base::Value::Type::DICTIONARY);
value.SetKey(site_settings::kDisabled, base::Value(state.disabled));
value.SetKey(
site_settings::kPolicyIndicator,
base::Value(site_settings::PolicyIndicatorTypeToString(state.indicator)));
return value;
}
// 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::ListValue* exceptions) {
std::unique_ptr<base::DictionaryValue> exception(new base::DictionaryValue());
std::string setting_string =
content_settings::ContentSettingToString(CONTENT_SETTING_ALLOW);
DCHECK(!setting_string.empty());
exception->SetString(kSetting, setting_string);
exception->SetString(kOrigin, url_pattern);
exception->SetString(kDisplayName, url_pattern);
exception->SetString(kEmbeddingOrigin, url_pattern);
exception->SetString(
kSource, SiteSettingSourceToString(SiteSettingSource::kHostedApp));
exception->SetBoolean(kIncognito, false);
exception->SetString(kAppName, app.name());
exception->SetString(kAppId, app.id());
exceptions->Append(std::move(exception));
}
// Create a DictionaryValue* that will act as a data source for a single row
// in a HostContentSettingsMap-controlled exceptions table (e.g., cookies).
std::unique_ptr<base::DictionaryValue> 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) {
auto exception = std::make_unique<base::DictionaryValue>();
exception->SetString(kOrigin, pattern.ToString());
exception->SetString(kDisplayName, display_name);
exception->SetString(kEmbeddingOrigin,
secondary_pattern == ContentSettingsPattern::Wildcard()
? std::string()
: secondary_pattern.ToString());
std::string setting_string =
content_settings::ContentSettingToString(setting);
DCHECK(!setting_string.empty());
exception->SetString(kSetting, setting_string);
exception->SetString(kSource, provider_name);
exception->SetBoolean(kIncognito, incognito);
exception->SetBoolean(kIsEmbargoed, is_embargoed);
return exception;
}
std::string GetDisplayNameForExtension(
const GURL& url,
const extensions::ExtensionRegistry* extension_registry) {
if (extension_registry && url.SchemeIs(extensions::kExtensionScheme)) {
// For the extension scheme, the pattern must be a valid URL.
DCHECK(url.is_valid());
const extensions::Extension* extension =
extension_registry->GetExtensionById(
url.host(), extensions::ExtensionRegistry::EVERYTHING);
if (extension)
return extension->name();
}
return std::string();
}
// Takes |url| and converts it into an individual origin string or retrieves
// name of the extension it belongs to.
std::string GetDisplayNameForGURL(
const GURL& url,
const extensions::ExtensionRegistry* extension_registry) {
const url::Origin origin = url::Origin::Create(url);
if (origin.opaque())
return url.spec();
std::string display_name =
GetDisplayNameForExtension(url, extension_registry);
if (!display_name.empty())
return display_name;
auto url_16 = url_formatter::FormatUrl(
url,
url_formatter::kFormatUrlOmitDefaults |
url_formatter::kFormatUrlOmitHTTPS |
url_formatter::kFormatUrlOmitTrailingSlashOnBareHostname,
net::UnescapeRule::NONE, nullptr, nullptr, nullptr);
auto url_string = base::UTF16ToUTF8(url_16);
return url_string;
}
// If the given |pattern| represents an individual origin or extension, retrieve
// a string to display it as such. If not, return the pattern as a string.
std::string GetDisplayNameForPattern(
const ContentSettingsPattern& pattern,
const extensions::ExtensionRegistry* extension_registry) {
const GURL url(pattern.ToString());
const std::string extension_display_name =
GetDisplayNameForExtension(url, extension_registry);
if (!extension_display_name.empty())
return extension_display_name;
return pattern.ToString();
}
void GetExceptionsForContentType(
ContentSettingsType type,
Profile* profile,
const extensions::ExtensionRegistry* extension_registry,
content::WebUI* web_ui,
bool incognito,
base::ListValue* 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;
}
all_patterns_settings[std::make_pair(
setting.primary_pattern, setting.source)][setting.secondary_pattern] =
setting.GetContentSetting();
}
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::PermissionUtil::IsPermission(type))
continue;
if (auto_blocker
->GetEmbargoResult(GURL(setting.primary_pattern.ToString()), type)
.content_setting == CONTENT_SETTING_BLOCK) {
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<std::unique_ptr<base::DictionaryValue>>
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(primary_pattern, extension_registry);
// The "parent" entry either has an identical primary and secondary pattern,
// or has a wildcard secondary. The two cases are indistinguishable in the
// UI.
auto parent = one_settings.find(primary_pattern);
if (parent == one_settings.end())
parent = one_settings.find(ContentSettingsPattern::Wildcard());
auto& this_provider_exceptions = all_provider_exceptions
[HostContentSettingsMap::GetProviderTypeFromSource(source)];
// Add the "parent" entry for the non-embedded setting.
ContentSetting parent_setting =
parent == one_settings.end() ? CONTENT_SETTING_DEFAULT : parent->second;
const ContentSettingsPattern& secondary_pattern =
parent == one_settings.end() ? primary_pattern : parent->first;
this_provider_exceptions.push_back(GetExceptionForPage(
type, profile, primary_pattern, secondary_pattern, display_name,
parent_setting, source, incognito,
base::Contains(origins_under_embargo, primary_pattern)));
// Add the "children" for any embedded settings.
for (auto j = one_settings.begin(); j != one_settings.end(); ++j) {
// Skip the non-embedded setting which we already added above.
if (j == parent)
continue;
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, extension_registry, web_ui,
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::DictionaryValue* object) {
std::string provider;
std::string setting = content_settings::ContentSettingToString(
map->GetDefaultContentSetting(content_type, &provider));
DCHECK(!setting.empty());
object->SetString(kSetting, setting);
if (provider != SiteSettingSourceToString(SiteSettingSource::kDefault))
object->SetString(kSource, provider);
}
ContentSetting GetContentSettingForOrigin(
Profile* profile,
const HostContentSettingsMap* map,
const GURL& origin,
ContentSettingsType content_type,
std::string* source_string,
const extensions::ExtensionRegistry* extension_registry,
std::string* display_name) {
// 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_SETTING_DEFAULT,
permissions::PermissionStatusSource::UNSPECIFIED);
if (permissions::PermissionUtil::IsPermission(content_type)) {
result =
PermissionManagerFactory::GetForProfile(profile)->GetPermissionStatus(
content_type, origin, origin);
} else {
DCHECK_EQ(base::Value::Type::INTEGER, value.type());
result.content_setting = content_settings::ValueToContentSetting(value);
}
// Retrieve the source of the content setting.
*source_string = SiteSettingSourceToString(
CalculateSiteSettingSource(profile, content_type, origin, info, result));
*display_name = GetDisplayNameForGURL(origin, extension_registry);
if (info.session_model == content_settings::SessionModel::OneTime) {
DCHECK_EQ(content_type, ContentSettingsType::GEOLOCATION);
DCHECK_EQ(result.content_setting, CONTENT_SETTING_ALLOW);
return CONTENT_SETTING_DEFAULT;
}
return result.content_setting;
}
std::vector<ContentSettingPatternSource> GetSiteExceptionsForContentType(
HostContentSettingsMap* map,
ContentSettingsType content_type) {
ContentSettingsForOneType entries;
map->GetSettingsForOneType(content_type, &entries);
entries.erase(std::remove_if(entries.begin(), entries.end(),
[](const ContentSettingPatternSource& e) {
return !PatternAppliesToSingleOrigin(e) ||
PatternAppliesToWebUISchemes(e);
}),
entries.end());
return entries;
}
void GetPolicyAllowedUrls(
ContentSettingsType type,
std::vector<std::unique_ptr<base::DictionaryValue>>* exceptions,
const extensions::ExtensionRegistry* extension_registry,
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* 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->GetList()) {
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(pattern, extension_registry);
exceptions->push_back(GetExceptionForPage(
type, profile, pattern, ContentSettingsPattern(), display_name,
CONTENT_SETTING_ALLOW,
SiteSettingSourceToString(SiteSettingSource::kPolicy), incognito));
}
}
const ChooserTypeNameEntry* ChooserTypeFromGroupName(const std::string& name) {
for (const auto& chooser_type : kChooserTypeGroupNames) {
if (chooser_type.name == name)
return &chooser_type;
}
return nullptr;
}
// Create a DictionaryValue* 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 CreateChooserExceptionObject(
const std::u16string& display_name,
const base::Value& object,
const std::string& chooser_type,
const ChooserExceptionDetails& chooser_exception_details) {
base::Value exception(base::Value::Type::DICTIONARY);
std::string setting_string =
content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT);
DCHECK(!setting_string.empty());
exception.SetStringKey(kDisplayName, display_name);
exception.SetKey(kObject, object.Clone());
exception.SetStringKey(kChooserType, chooser_type);
// Order the sites by the provider precedence order.
std::vector<base::Value>
all_provider_sites[HostContentSettingsMap::NUM_PROVIDER_TYPES];
for (const auto& details : chooser_exception_details) {
const GURL& requesting_origin = details.first.first;
const std::string& source = details.first.second;
auto& this_provider_sites =
all_provider_sites[HostContentSettingsMap::GetProviderTypeFromSource(
source)];
for (const auto& embedding_origin_incognito_pair : details.second) {
const GURL& embedding_origin = embedding_origin_incognito_pair.first;
const bool incognito = embedding_origin_incognito_pair.second;
base::Value site(base::Value::Type::DICTIONARY);
site.SetStringKey(kOrigin, requesting_origin.spec());
site.SetStringKey(kDisplayName, requesting_origin.spec());
site.SetStringKey(kEmbeddingOrigin, embedding_origin.is_empty()
? std::string()
: embedding_origin.spec());
site.SetStringKey(kSetting, setting_string);
site.SetStringKey(kSource, source);
site.SetBoolKey(kIncognito, incognito);
this_provider_sites.push_back(std::move(site));
}
}
base::Value sites(base::Value::Type::LIST);
for (auto& one_provider_sites : all_provider_sites) {
for (auto& site : one_provider_sites) {
sites.Append(std::move(site));
}
}
exception.SetKey(kSites, std::move(sites));
return exception;
}
base::Value GetChooserExceptionListFromProfile(
Profile* profile,
const ChooserTypeNameEntry& chooser_type) {
base::Value exceptions(base::Value::Type::LIST);
ContentSettingsType content_type =
ContentSettingsTypeFromGroupName(std::string(chooser_type.name));
// 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, object->value.Clone())];
std::string source = GetSourceStringForChooserException(
profile, content_type, object->source);
const auto origin_source_pair = std::make_pair(object->origin, source);
auto& origin_incognito_pair_set =
chooser_exception_details[origin_source_pair];
const auto origin_incognito_pair =
std::make_pair(object->origin, object->incognito);
origin_incognito_pair_set.insert(origin_incognito_pair);
}
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));
}
return exceptions;
}
std::string PolicyIndicatorTypeToString(const PolicyIndicatorType type) {
return kPolicyIndicatorTypeStringMapping[static_cast<int>(type)]
.indicator_str;
}
PolicyIndicatorType GetPolicyIndicatorFromPref(
const PrefService::Preference* pref) {
if (!pref) {
return PolicyIndicatorType::kNone;
}
if (pref->IsExtensionControlled()) {
return PolicyIndicatorType::kExtension;
}
if (pref->IsManagedByCustodian()) {
return PolicyIndicatorType::kParent;
}
if (pref->IsManaged()) {
return PolicyIndicatorType::kDevicePolicy;
}
if (pref->GetRecommendedValue()) {
return PolicyIndicatorType::kRecommended;
}
return PolicyIndicatorType::kNone;
}
} // namespace site_settings