| // 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 <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #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/notreached.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/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_features.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/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/ui_features.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/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/content_settings/core/browser/content_settings_provider.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_types.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_util.h" |
| #include "components/permissions/permissions_client.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/privacy_sandbox/privacy_sandbox_features.h" |
| #include "components/strings/grit/components_strings.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/elide_url.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 { |
| |
| using PermissionStatus = blink::mojom::PermissionStatus; |
| using content_settings::SettingSource; |
| |
| // 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::JAVASCRIPT_JIT, "javascript-jit"}, |
| {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-management"}, |
| {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"}, |
| {ContentSettingsType::AUTO_PICTURE_IN_PICTURE, "auto-picture-in-picture"}, |
| {ContentSettingsType::CAPTURED_SURFACE_CONTROL, "captured-surface-control"}, |
| {ContentSettingsType::WEB_PRINTING, "web-printing"}, |
| {ContentSettingsType::SPEAKER_SELECTION, "speaker-selection"}, |
| {ContentSettingsType::AUTOMATIC_FULLSCREEN, "automatic-fullscreen"}, |
| {ContentSettingsType::KEYBOARD_LOCK, "keyboard-lock"}, |
| {ContentSettingsType::POINTER_LOCK, "pointer-lock"}, |
| {ContentSettingsType::TRACKING_PROTECTION, "tracking-protection"}, |
| |
| // 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_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::HTTP_ALLOWED, nullptr}, |
| {ContentSettingsType::HTTPS_ENFORCED, nullptr}, |
| {ContentSettingsType::FORMFILL_METADATA, nullptr}, |
| {ContentSettingsType::DEPRECATED_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/40253587): 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}, |
| {ContentSettingsType::COOKIE_CONTROLS_METADATA, nullptr}, |
| {ContentSettingsType::TPCD_TRIAL, nullptr}, |
| {ContentSettingsType::TPCD_METADATA_GRANTS, nullptr}, |
| // TODO(crbug.com/40101962): Update the name once the design is finalized |
| // for the integration with Safety Hub. |
| {ContentSettingsType::FILE_SYSTEM_ACCESS_EXTENDED_PERMISSION, nullptr}, |
| {ContentSettingsType::TPCD_HEURISTICS_GRANTS, nullptr}, |
| {ContentSettingsType::FILE_SYSTEM_ACCESS_RESTORE_PERMISSION, nullptr}, |
| // TODO(crbug.com/40275778): Update name once UI design is done. |
| {ContentSettingsType::SMART_CARD_GUARD, nullptr}, |
| {ContentSettingsType::SMART_CARD_DATA, nullptr}, |
| {ContentSettingsType::TOP_LEVEL_TPCD_TRIAL, nullptr}, |
| {ContentSettingsType::SUB_APP_INSTALLATION_PROMPTS, nullptr}, |
| {ContentSettingsType::DIRECT_SOCKETS, nullptr}, |
| {ContentSettingsType::REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS, nullptr}, |
| {ContentSettingsType::TOP_LEVEL_TPCD_ORIGIN_TRIAL, nullptr}, |
| }; |
| |
| static_assert( |
| std::size(kContentSettingsTypeGroupNames) == |
| // Add one since the sequence is kMinValue = -1, 0, ..., kMaxValue |
| 1 + static_cast<int32_t>(ContentSettingsType::kMaxValue) - |
| static_cast<int32_t>(ContentSettingsType::kMinValue), |
| "kContentSettingsTypeGroupNames should have the correct number " |
| "of elements"); |
| |
| struct SiteSettingSourceStringMapping { |
| SiteSettingSource source; |
| const char* source_str; |
| }; |
| |
| // 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 content::PermissionResult result) { |
| if (info.source == SettingSource::kAllowList) { |
| return SiteSettingSource::kAllowlist; // Source #1. |
| } |
| |
| if (result.source == content::PermissionStatusSource::KILL_SWITCH) { |
| return SiteSettingSource::kKillSwitch; // Source #2. |
| } |
| |
| if (result.source == content::PermissionStatusSource::INSECURE_ORIGIN) { |
| return SiteSettingSource::kInsecureOrigin; // Source #3. |
| } |
| |
| if (info.source == SettingSource::kPolicy || |
| info.source == SettingSource::kSupervised) { |
| return SiteSettingSource::kPolicy; // Source #4. |
| } |
| |
| if (info.source == SettingSource::kExtension) { |
| 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(SettingSource::kNone, info.source); |
| if (info.source == SettingSource::kUser) { |
| if (result.source == content::PermissionStatusSource::MULTIPLE_DISMISSALS || |
| result.source == content::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 IsFromWebUIAllowlistSource(const ContentSettingPatternSource& pattern) { |
| return HostContentSettingsMap::GetProviderTypeFromSource(pattern.source) == |
| content_settings::ProviderType::kWebuiAllowlistProvider; |
| } |
| |
| // 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.is_valid() && (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, SiteSettingSource::kPolicy, |
| // Pass base::Time() to indicate the exceptions do not expire. |
| base::Time(), 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. |
| SiteSettingSource GetSourceForChooserException( |
| 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. |
| content::PermissionResult permission_result( |
| PermissionStatus::ASK, content::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(), info, permission_result); |
| DCHECK(calculated_source == SiteSettingSource::kPolicy || |
| calculated_source == SiteSettingSource::kPreference); |
| return 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(crbug.com/40458188): 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}; |
| |
| } // 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(std::string_view 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 std::string_view will crash, so we have to handle it explicitly |
| // before comparing. |
| if (entry.name != nullptr && entry.name == name) { |
| return entry.type; |
| } |
| } |
| |
| return ContentSettingsType::DEFAULT; |
| } |
| |
| std::string_view ContentSettingsTypeToGroupName(ContentSettingsType type) { |
| for (const auto& entry : kContentSettingsTypeGroupNames) { |
| if (type == entry.type) { |
| // Content setting types that aren't represented in the settings UI |
| // will have `nullptr` as their `name`. Although they are valid content |
| // settings types, they don't have a readable name. |
| // TODO(crbug.com/40066645): Replace LOG with CHECK. |
| if (!entry.name) { |
| LOG(ERROR) << static_cast<int32_t>(type) |
| << " does not have a readable name."; |
| } |
| |
| return entry.name ? entry.name : std::string_view(); |
| } |
| } |
| |
| NOTREACHED() << static_cast<int32_t>(type) |
| << " is not a recognized content settings type."; |
| return std::string_view(); |
| } |
| |
| std::vector<ContentSettingsType> GetVisiblePermissionCategories( |
| const std::string& origin, |
| Profile* profile) { |
| // 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::JAVASCRIPT_JIT, |
| 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); |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| blink::features::kMediaSessionEnterPictureInPicture)) { |
| base_types->push_back(ContentSettingsType::AUTO_PICTURE_IN_PICTURE); |
| } |
| |
| if (base::FeatureList::IsEnabled(blink::features::kWebPrinting)) { |
| base_types->push_back(ContentSettingsType::WEB_PRINTING); |
| } |
| |
| if (base::FeatureList::IsEnabled(blink::features::kSpeakerSelection)) { |
| base_types->push_back(ContentSettingsType::SPEAKER_SELECTION); |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| features::kCapturedSurfaceControlKillswitch) && |
| base::FeatureList::IsEnabled( |
| features::kCapturedSurfaceControlStickyPermissions)) { |
| base_types->push_back(ContentSettingsType::CAPTURED_SURFACE_CONTROL); |
| } |
| |
| if (base::FeatureList::IsEnabled(features::kKeyboardAndPointerLockPrompt)) { |
| base_types->push_back(ContentSettingsType::KEYBOARD_LOCK); |
| base_types->push_back(ContentSettingsType::POINTER_LOCK); |
| } |
| |
| initialized = true; |
| } |
| |
| // The permission categories below are only shown for certain origins. |
| std::vector<ContentSettingsType> types_for_origin = *base_types; |
| if (base::FeatureList::IsEnabled( |
| features::kAutomaticFullscreenContentSetting)) { |
| // Show for non-origin-specific lists, IWAs, and non-default values. |
| if (origin.empty() || GURL(origin).SchemeIs(chrome::kIsolatedAppScheme)) { |
| types_for_origin.push_back(ContentSettingsType::AUTOMATIC_FULLSCREEN); |
| } else if (profile) { |
| SiteSettingSource source; |
| GetContentSettingForOrigin( |
| profile, HostContentSettingsMapFactory::GetForProfile(profile), |
| GURL(origin), ContentSettingsType::AUTOMATIC_FULLSCREEN, &source); |
| if (source != SiteSettingSource::kDefault) { |
| types_for_origin.push_back(ContentSettingsType::AUTOMATIC_FULLSCREEN); |
| } |
| } |
| } |
| |
| return types_for_origin; |
| } |
| |
| std::string SiteSettingSourceToString(const SiteSettingSource source) { |
| switch (source) { |
| case SiteSettingSource::kAllowlist: |
| return "allowlist"; |
| case SiteSettingSource::kAdsFilterBlocklist: |
| return "ads-filter-blacklist"; |
| case SiteSettingSource::kDefault: |
| return "default"; |
| case SiteSettingSource::kEmbargo: |
| return "embargo"; |
| case SiteSettingSource::kExtension: |
| return "extension"; |
| case SiteSettingSource::kHostedApp: |
| return "HostedApp"; |
| case SiteSettingSource::kInsecureOrigin: |
| return "insecure-origin"; |
| case SiteSettingSource::kKillSwitch: |
| return "kill-switch"; |
| case SiteSettingSource::kPolicy: |
| return "policy"; |
| case SiteSettingSource::kPreference: |
| return "preference"; |
| case SiteSettingSource::kNumSources: |
| NOTREACHED(); |
| return ""; |
| } |
| } |
| |
| SiteSettingSource ProviderTypeToSiteSettingsSource( |
| const content_settings::ProviderType provider_type) { |
| switch (provider_type) { |
| case content_settings::ProviderType::kWebuiAllowlistProvider: |
| return SiteSettingSource::kAllowlist; |
| case content_settings::ProviderType::kPolicyProvider: |
| case content_settings::ProviderType::kSupervisedProvider: |
| return SiteSettingSource::kPolicy; |
| case content_settings::ProviderType::kCustomExtensionProvider: |
| return SiteSettingSource::kExtension; |
| case content_settings::ProviderType::kInstalledWebappProvider: |
| return SiteSettingSource::kHostedApp; |
| case content_settings::ProviderType::kOneTimePermissionProvider: |
| case content_settings::ProviderType::kPrefProvider: |
| return SiteSettingSource::kPreference; |
| case content_settings::ProviderType::kDefaultProvider: |
| return SiteSettingSource::kDefault; |
| |
| case content_settings::ProviderType::kNotificationAndroidProvider: |
| case content_settings::ProviderType::kProviderForTests: |
| case content_settings::ProviderType::kOtherProviderForTests: |
| NOTREACHED(); |
| return SiteSettingSource::kPreference; |
| } |
| } |
| |
| // 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, |
| SiteSettingSource source, |
| bool incognito, |
| bool is_embargoed) { |
| base::Value::Dict exception; |
| exception.Set(kOrigin, origin); |
| // TODO(crbug.com/40101962): 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, SiteSettingSourceToString(source)); |
| exception.Set(kIncognito, incognito); |
| exception.Set(kIsEmbargoed, is_embargoed); |
| return exception; |
| } |
| |
| std::u16string GetExpirationDescription(const base::Time& expiration) { |
| CHECK(!expiration.is_null()); |
| |
| const base::TimeDelta time_diff = |
| expiration.LocalMidnight() - base::Time::Now().LocalMidnight(); |
| |
| // Only exceptions that haven't expired should reach this function. |
| // However, there is an edge case where an exception could expire between |
| // being fetched and this calculation. So let's always return a valid |
| // number, zero. |
| int days = std::max(time_diff.InDays(), 0); |
| |
| return l10n_util::GetPluralStringFUTF16(IDS_SETTINGS_EXPIRES_AFTER_TIME_LABEL, |
| days); |
| } |
| |
| // 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 SiteSettingSource source, |
| const base::Time& expiration, |
| 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); |
| |
| // Cookie exception types may have an expiration that should be shown. |
| if ((content_type == ContentSettingsType::COOKIES || |
| content_type == ContentSettingsType::TRACKING_PROTECTION) && |
| !expiration.is_null() && !incognito) { |
| exception.Set(kDescription, GetExpirationDescription(expiration)); |
| } |
| |
| exception.Set(kSource, SiteSettingSourceToString(source)); |
| exception.Set(kIncognito, incognito); |
| exception.Set(kIsEmbargoed, is_embargoed); |
| return exception; |
| } |
| |
| std::u16string GetStorageAccessEmbeddingDescription( |
| StorageAccessEmbeddingException embedding_sa_exception) { |
| if (embedding_sa_exception.is_embargoed) { |
| return l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_PERMISSION_AUTOMATICALLY_BLOCKED); |
| } |
| |
| if (embedding_sa_exception.expiration.is_null()) { |
| return std::u16string(); |
| } |
| |
| return GetExpirationDescription(embedding_sa_exception.expiration); |
| } |
| |
| // 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 without wildcards as a string. |
| std::string GetStorageAccessDisplayNameForPattern( |
| Profile* profile, |
| ContentSettingsPattern pattern) { |
| GURL url(pattern.ToString()); |
| if (url.is_valid() && (url.SchemeIs(extensions::kExtensionScheme) || |
| url.SchemeIs(chrome::kIsolatedAppScheme))) { |
| return GetDisplayNameForGURL(profile, url, /*hostname_only=*/false); |
| } |
| |
| GURL url2 = pattern.ToRepresentativeUrl(); |
| if (url2.is_valid()) { |
| return base::UTF16ToUTF8(FormatUrlForSecurityDisplay( |
| url2, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC)); |
| } |
| |
| return pattern.ToString(); |
| } |
| |
| base::Value::Dict GetStorageAccessExceptionForPage( |
| Profile* profile, |
| const ContentSettingsPattern& pattern, |
| const std::string& display_name, |
| ContentSetting setting, |
| const std::vector<StorageAccessEmbeddingException>& exceptions) { |
| CHECK(!exceptions.empty()); |
| |
| base::Value::Dict exception; |
| exception.Set(kOrigin, pattern.ToString()); |
| exception.Set(kDisplayName, display_name); |
| std::string setting_string = |
| content_settings::ContentSettingToString(setting); |
| DCHECK(!setting_string.empty()); |
| exception.Set(kSetting, setting_string); |
| |
| // If there is only one exception and that exception applies everywhere, |
| // i.e. `secondary_pattern` is empty, then don't return exceptions and a |
| // static row should be displayed. In practice, this only applies to embargoed |
| // sites. |
| if (exceptions.size() == 1 && |
| exceptions[0].secondary_pattern == ContentSettingsPattern::Wildcard()) { |
| auto& embedding_sa_exception = exceptions[0]; |
| |
| std::u16string description = |
| GetStorageAccessEmbeddingDescription(embedding_sa_exception); |
| if (!description.empty()) { |
| exception.Set(kDescription, description); |
| } |
| |
| exception.Set(kIncognito, embedding_sa_exception.is_incognito); |
| exception.Set(kExceptions, base::Value::List()); |
| return exception; |
| } |
| |
| exception.Set(kCloseDescription, |
| l10n_util::GetPluralStringFUTF16(IDS_DEL_SITE_SETTINGS_COUNTER, |
| exceptions.size())); |
| const int open_description_id = |
| (setting == ContentSetting::CONTENT_SETTING_ALLOW) |
| ? IDS_SETTINGS_STORAGE_ACCESS_ALLOWED_SITE_LABEL |
| : IDS_SETTINGS_STORAGE_ACCESS_BLOCKED_SITE_LABEL; |
| exception.Set(kOpenDescription, |
| l10n_util::GetStringUTF16(open_description_id)); |
| |
| base::Value::List embedding_origins; |
| for (auto& embedding_sa_exception : exceptions) { |
| ContentSettingsPattern secondary_pattern = |
| embedding_sa_exception.secondary_pattern; |
| base::Value::Dict embedding_exception; |
| embedding_exception.Set( |
| kEmbeddingOrigin, |
| secondary_pattern == ContentSettingsPattern::Wildcard() |
| ? std::string() |
| : secondary_pattern.ToString()); |
| embedding_exception.Set( |
| kEmbeddingDisplayName, |
| GetStorageAccessDisplayNameForPattern(profile, secondary_pattern)); |
| |
| std::u16string description = |
| GetStorageAccessEmbeddingDescription(embedding_sa_exception); |
| if (!description.empty()) { |
| embedding_exception.Set(kDescription, description); |
| } |
| embedding_exception.Set(kIncognito, embedding_sa_exception.is_incognito); |
| embedding_origins.Append(std::move(embedding_exception)); |
| } |
| |
| exception.Set(kExceptions, std::move(embedding_origins)); |
| |
| 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); |
| } |
| |
| using RawPatternSettings = |
| std::map<std::pair<ContentSettingsPattern, content_settings::ProviderType>, |
| OnePatternSettings, |
| std::greater<>>; |
| |
| // Fills in `all_patterns_settings` with site exceptions information for the |
| // given `type` from `profile`. |
| void GetRawExceptionsForContentSettingsType( |
| ContentSettingsType type, |
| Profile* profile, |
| content::WebUI* web_ui, |
| RawPatternSettings& all_patterns_settings) { |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| for (const auto& setting : map->GetSettingsForOneType(type)) { |
| // 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, excluding policy-source exceptions as policies cannot specify |
| // incognito-only exceptions, meaning these are necesssarily duplicates. |
| if (map->IsOffTheRecord() && |
| (!setting.incognito || |
| setting.source == |
| SiteSettingSourceToString(SiteSettingSource::kPolicy))) { |
| continue; |
| } |
| |
| // Don't add allowlisted settings. |
| if (IsFromWebUIAllowlistSource(setting)) { |
| continue; |
| } |
| |
| // Don't add auto-granted permissions for storage access exceptions. |
| if (IsGrantedByRelatedWebsiteSets(type, setting.metadata) && |
| !base::FeatureList::IsEnabled( |
| permissions::features::kShowRelatedWebsiteSetsPermissionGrants)) { |
| continue; |
| } |
| |
| auto content_setting = setting.GetContentSetting(); |
| // There is no user-facing concept of SESSION_ONLY cookie exceptions that |
| // use secondary patterns. These are instead presented as ALLOW. |
| // TODO(crbug.com/40251893): Perform a one time migration of the actual |
| // content settings when the extension API no-longer allows them to be |
| // created. |
| if (type == ContentSettingsType::COOKIES && |
| content_setting == ContentSetting::CONTENT_SETTING_SESSION_ONLY && |
| setting.secondary_pattern != ContentSettingsPattern::Wildcard()) { |
| content_setting = ContentSetting::CONTENT_SETTING_ALLOW; |
| } |
| |
| all_patterns_settings[{ |
| setting.primary_pattern, |
| HostContentSettingsMap::GetProviderTypeFromSource(setting.source)}][{ |
| setting.secondary_pattern, setting.incognito}] = { |
| content_setting, /*is_embargoed=*/false, setting.metadata.expiration()}; |
| } |
| |
| permissions::PermissionDecisionAutoBlocker* auto_blocker = |
| permissions::PermissionsClient::Get()->GetPermissionDecisionAutoBlocker( |
| profile); |
| |
| for (const auto& setting : map->GetSettingsForOneType( |
| ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA)) { |
| // 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)) { |
| all_patterns_settings[{ |
| setting.primary_pattern, |
| HostContentSettingsMap::GetProviderTypeFromSource(setting.source)}] |
| [{setting.secondary_pattern, setting.incognito}] = { |
| CONTENT_SETTING_BLOCK, /*is_embargoed=*/true, |
| setting.metadata.expiration()}; |
| } |
| } |
| } |
| |
| void Append3pcExceptions(Profile* profile, |
| content::WebUI* web_ui, |
| base::Value::List* exceptions) { |
| base::Value::List cookie_exceptions; |
| GetExceptionsForContentType(ContentSettingsType::COOKIES, profile, web_ui, |
| /*incognito=*/false, &cookie_exceptions); |
| // Create a set of TP exceptions keyed off of <name, source>. |
| std::set<std::pair<std::string, std::string>> display_name_and_source; |
| for (const auto& exception : *exceptions) { |
| const auto& dict = exception.GetDict(); |
| CHECK(dict.contains(kEmbeddingOrigin) && dict.contains(kSource)); |
| display_name_and_source.insert(std::make_pair( |
| *dict.FindString(kEmbeddingOrigin), *dict.FindString(kSource))); |
| } |
| |
| for (auto& cookie_exception : cookie_exceptions) { |
| auto& dict = cookie_exception.GetDict(); |
| CHECK(dict.contains(kEmbeddingOrigin) && dict.contains(kSource) && |
| dict.contains(kOrigin)); |
| // Add 3PC exceptions that are not also TP exceptions. |
| if (*dict.FindString(kOrigin) == "*" && |
| !display_name_and_source.contains(std::make_pair( |
| *dict.FindString(kEmbeddingOrigin), *dict.FindString(kSource)))) { |
| dict.Set(kDescription, |
| l10n_util::GetStringUTF16( |
| IDS_SETTINGS_THIRD_PARTY_COOKIES_ONLY_EXCEPTION_LABEL)); |
| exceptions->Append(std::move(cookie_exception)); |
| } |
| } |
| } |
| |
| void GetExceptionsForContentType(ContentSettingsType type, |
| Profile* profile, |
| content::WebUI* web_ui, |
| bool incognito, |
| base::Value::List* exceptions) { |
| // Group settings by primary_pattern. |
| RawPatternSettings all_patterns_settings; |
| |
| GetRawExceptionsForContentSettingsType(type, profile, web_ui, |
| all_patterns_settings); |
| |
| // Keep the exceptions sorted by provider so they will be displayed in |
| // precedence order. |
| std::map<content_settings::ProviderType, std::vector<base::Value::Dict>> |
| all_provider_exceptions; |
| |
| for (const auto& [primary_pattern_and_source, one_settings] : |
| 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[source]; |
| |
| for (const auto& secondary_setting : one_settings) { |
| const SiteExceptionInfo& site_exception_info = secondary_setting.second; |
| const auto& [secondary_pattern, is_incognito] = secondary_setting.first; |
| this_provider_exceptions.push_back(GetExceptionForPage( |
| type, profile, primary_pattern, secondary_pattern, |
| std::move(display_name), site_exception_info.content_setting, |
| ProviderTypeToSiteSettingsSource(source), |
| site_exception_info.expiration, is_incognito, |
| site_exception_info.is_embargoed)); |
| } |
| } |
| |
| // 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 |
| [content_settings::ProviderType::kPolicyProvider]; |
| 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 |
| [content_settings::ProviderType::kDefaultProvider]; |
| GetFileSystemGrantedEntries(&urls_with_granted_entries, profile, incognito); |
| } |
| |
| for (auto& one_provider_exceptions : all_provider_exceptions) { |
| for (auto& exception : one_provider_exceptions.second) { |
| exceptions->Append(std::move(exception)); |
| } |
| } |
| |
| // The TP exceptions list should also contain 3PC exceptions. |
| if (type == ContentSettingsType::TRACKING_PROTECTION) { |
| Append3pcExceptions(profile, web_ui, exceptions); |
| } |
| } |
| |
| void GetStorageAccessExceptions(ContentSetting content_setting, |
| Profile* profile, |
| Profile* incognito_profile, |
| content::WebUI* web_ui, |
| base::Value::List* exceptions) { |
| ContentSettingsType type = ContentSettingsType::STORAGE_ACCESS; |
| |
| // Group settings by primary_pattern. |
| RawPatternSettings all_patterns_settings; |
| |
| GetRawExceptionsForContentSettingsType(type, profile, web_ui, |
| all_patterns_settings); |
| |
| if (incognito_profile) { |
| GetRawExceptionsForContentSettingsType(type, incognito_profile, web_ui, |
| all_patterns_settings); |
| } |
| |
| for (const auto& [primary_pattern_and_source, one_settings] : |
| all_patterns_settings) { |
| const auto& [primary_pattern, source] = primary_pattern_and_source; |
| |
| std::vector<StorageAccessEmbeddingException> sa_exceptions; |
| |
| for (const auto& secondary_setting : one_settings) { |
| const SiteExceptionInfo& site_exception_info = secondary_setting.second; |
| const auto& [secondary_pattern, is_incognito] = secondary_setting.first; |
| |
| if (site_exception_info.content_setting != content_setting) { |
| continue; |
| } |
| |
| sa_exceptions.push_back({secondary_pattern, is_incognito, |
| site_exception_info.is_embargoed, |
| site_exception_info.expiration}); |
| } |
| |
| if (sa_exceptions.empty()) { |
| continue; |
| } |
| |
| // TODO(http://b/289788055): Remove wildcards. |
| const std::string display_name = |
| GetStorageAccessDisplayNameForPattern(profile, primary_pattern); |
| |
| exceptions->Append(GetStorageAccessExceptionForPage( |
| profile, primary_pattern, std::move(display_name), content_setting, |
| sa_exceptions)); |
| } |
| } |
| |
| void GetContentCategorySetting(const HostContentSettingsMap* map, |
| ContentSettingsType content_type, |
| base::Value::Dict* object) { |
| auto provider = content_settings::ProviderType::kDefaultProvider; |
| std::string setting = content_settings::ContentSettingToString( |
| map->GetDefaultContentSetting(content_type, &provider)); |
| DCHECK(!setting.empty()); |
| |
| object->Set(kSetting, setting); |
| |
| SiteSettingSource source = ProviderTypeToSiteSettingsSource(provider); |
| if (source != SiteSettingSource::kDefault) { |
| object->Set(kSource, SiteSettingSourceToString(source)); |
| } |
| } |
| |
| ContentSetting GetContentSettingForOrigin(Profile* profile, |
| const HostContentSettingsMap* map, |
| const GURL& origin, |
| ContentSettingsType content_type, |
| SiteSettingSource* source) { |
| // 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; |
| ContentSetting setting = |
| map->GetContentSetting(origin, origin, content_type, &info); |
| |
| // Retrieve the content setting. |
| content::PermissionResult result( |
| permissions::PermissionUtil::ContentSettingToPermissionStatus(setting), |
| content::PermissionStatusSource::UNSPECIFIED); |
| if (permissions::PermissionDecisionAutoBlocker::IsEnabledForContentSetting( |
| content_type)) { |
| if (permissions::PermissionUtil::IsPermission(content_type)) { |
| result = profile->GetPermissionController() |
| ->GetPermissionResultForOriginWithoutContext( |
| permissions::PermissionUtil:: |
| ContentSettingTypeToPermissionType(content_type), |
| url::Origin::Create(origin)); |
| } else { |
| permissions::PermissionDecisionAutoBlocker* auto_blocker = |
| permissions::PermissionsClient::Get() |
| ->GetPermissionDecisionAutoBlocker(profile); |
| std::optional<content::PermissionResult> embargo_result = |
| auto_blocker->GetEmbargoResult(origin, content_type); |
| if (embargo_result) { |
| result = embargo_result.value(); |
| } |
| } |
| } |
| |
| // Retrieve the source of the content setting. |
| *source = |
| CalculateSiteSettingSource(profile, content_type, origin, info, result); |
| |
| if (info.metadata.session_model() == |
| content_settings::mojom::SessionModel::ONE_TIME) { |
| DCHECK( |
| permissions::PermissionUtil::CanPermissionBeAllowedOnce(content_type)); |
| DCHECK_EQ(result.status, PermissionStatus::GRANTED); |
| return CONTENT_SETTING_DEFAULT; |
| } |
| return permissions::PermissionUtil::PermissionStatusToContentSetting( |
| result.status); |
| } |
| |
| std::vector<ContentSettingPatternSource> |
| GetSingleOriginExceptionsForContentType(HostContentSettingsMap* map, |
| ContentSettingsType content_type) { |
| ContentSettingsForOneType entries = map->GetSettingsForOneType(content_type); |
| // Exclude any entries that are allowlisted or don't represent a single |
| // top-frame origin. |
| std::erase_if(entries, [](const ContentSettingPatternSource& e) { |
| return !content_settings::PatternAppliesToSingleOrigin( |
| e.primary_pattern, e.secondary_pattern) || |
| IsFromWebUIAllowlistSource(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, 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(std::string_view 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::map<SiteSettingSource, std::vector<base::Value::Dict>> |
| all_provider_sites; |
| for (const auto& details : chooser_exception_details) { |
| const GURL& origin = std::get<0>(details); |
| const SiteSettingSource 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[source]; |
| base::Value::Dict site; |
| site.Set(kOrigin, origin.spec()); |
| site.Set(kDisplayName, site_display_name); |
| site.Set(kSetting, setting_string); |
| site.Set(kSource, SiteSettingSourceToString(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.second) { |
| 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(crbug.com/40458188): 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()))]; |
| |
| SiteSettingSource source = |
| GetSourceForChooserException(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; |
| } |
| |
| } // namespace site_settings |