blob: a79e82b942b470ae553b7d239e13ad5baff5c0df [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/site_settings_helper.h"
#include <functional>
#include <string>
#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "chrome/browser/permissions/chooser_context_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/prefs/pref_service.h"
#include "extensions/browser/extension_registry.h"
namespace site_settings {
const char kAppName[] = "appName";
const char kAppId[] = "appId";
const char kSetting[] = "setting";
const char kOrigin[] = "origin";
const char kDisplayName[] = "displayName";
const char kOriginForFavicon[] = "originForFavicon";
const char kPolicyProviderId[] = "policy";
const char kSource[] = "source";
const char kIncognito[] = "incognito";
const char kEmbeddingOrigin[] = "embeddingOrigin";
const char kPreferencesSource[] = "preference";
const char kObject[] = "object";
const char kObjectName[] = "objectName";
const char kGroupTypeUsb[] = "usb-devices";
ChooserContextBase* GetUsbChooserContext(Profile* profile) {
return reinterpret_cast<ChooserContextBase*>(
UsbChooserContextFactory::GetForProfile(profile));
}
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;
const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = {
{CONTENT_SETTINGS_TYPE_COOKIES, "cookies"},
{CONTENT_SETTINGS_TYPE_IMAGES, "images"},
{CONTENT_SETTINGS_TYPE_JAVASCRIPT, "javascript"},
{CONTENT_SETTINGS_TYPE_PLUGINS, "plugins"},
{CONTENT_SETTINGS_TYPE_POPUPS, "popups"},
{CONTENT_SETTINGS_TYPE_GEOLOCATION, "location"},
{CONTENT_SETTINGS_TYPE_NOTIFICATIONS, "notifications"},
{CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE, "auto-select-certificate"},
{CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS, "register-protocol-handler"},
{CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, "media-stream-mic"},
{CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, "media-stream-camera"},
{CONTENT_SETTINGS_TYPE_PPAPI_BROKER, "ppapi-broker"},
{CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS, "multiple-automatic-downloads"},
{CONTENT_SETTINGS_TYPE_MIDI_SYSEX, "midi-sysex"},
{CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "ssl-cert-decisions"},
#if defined(OS_CHROMEOS)
{CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER, "protectedContent"},
#endif
{CONTENT_SETTINGS_TYPE_BACKGROUND_SYNC, "background-sync"},
};
} // namespace
bool HasRegisteredGroupName(ContentSettingsType type) {
for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
if (type == kContentSettingsTypeGroupNames[i].type)
return true;
}
return false;
}
ContentSettingsType ContentSettingsTypeFromGroupName(const std::string& name) {
for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
if (name == kContentSettingsTypeGroupNames[i].name)
return kContentSettingsTypeGroupNames[i].type;
}
NOTREACHED() << name << " is not a recognized content settings type.";
return CONTENT_SETTINGS_TYPE_DEFAULT;
}
std::string ContentSettingsTypeToGroupName(ContentSettingsType type) {
for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
if (type == kContentSettingsTypeGroupNames[i].type)
return kContentSettingsTypeGroupNames[i].name;
}
NOTREACHED() << type << " is not a recognized content settings type.";
return std::string();
}
// 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(site_settings::kSetting, setting_string);
exception->SetString(site_settings::kOrigin, url_pattern);
exception->SetString(site_settings::kDisplayName, url_pattern);
exception->SetString(site_settings::kEmbeddingOrigin, url_pattern);
exception->SetString(site_settings::kSource, "HostedApp");
exception->SetBoolean(site_settings::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(
const ContentSettingsPattern& pattern,
const ContentSettingsPattern& secondary_pattern,
const std::string& display_name,
const ContentSetting& setting,
const std::string& provider_name,
bool incognito) {
base::DictionaryValue* exception = new 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);
return base::WrapUnique(exception);
}
std::string GetDisplayName(
const ContentSettingsPattern& pattern,
const extensions::ExtensionRegistry* extension_registry) {
if (extension_registry &&
pattern.GetScheme() == ContentSettingsPattern::SCHEME_CHROMEEXTENSION) {
GURL url(pattern.ToString());
// 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 pattern.ToString();
}
void GetExceptionsFromHostContentSettingsMap(
const HostContentSettingsMap* map,
ContentSettingsType type,
const extensions::ExtensionRegistry* extension_registry,
content::WebUI* web_ui,
bool incognito,
const std::string* filter,
base::ListValue* exceptions) {
ContentSettingsForOneType entries;
map->GetSettingsForOneType(type, std::string(), &entries);
// Group settings by primary_pattern.
AllPatternsSettings all_patterns_settings;
for (ContentSettingsForOneType::iterator i = entries.begin();
i != entries.end(); ++i) {
// Don't add default settings.
if (i->primary_pattern == ContentSettingsPattern::Wildcard() &&
i->secondary_pattern == ContentSettingsPattern::Wildcard() &&
i->source != kPreferencesSource) {
continue;
}
// Off-the-record HostContentSettingsMap contains incognito content settings
// as well as normal content settings. Here, we use the incongnito settings
// only.
if (map->is_incognito() && !i->incognito)
continue;
if (filter && i->primary_pattern.ToString() != *filter)
continue;
all_patterns_settings[std::make_pair(i->primary_pattern, i->source)]
[i->secondary_pattern] = i->setting;
}
// 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 (AllPatternsSettings::reverse_iterator i = all_patterns_settings.rbegin();
i != all_patterns_settings.rend();
++i) {
const ContentSettingsPattern& primary_pattern = i->first.first;
const OnePatternSettings& one_settings = i->second;
const std::string display_name =
GetDisplayName(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.
OnePatternSettings::const_iterator parent =
one_settings.find(primary_pattern);
if (parent == one_settings.end())
parent = one_settings.find(ContentSettingsPattern::Wildcard());
const std::string& source = i->first.second;
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(primary_pattern, secondary_pattern, display_name,
parent_setting, source, incognito));
// Add the "children" for any embedded settings.
for (OnePatternSettings::const_iterator 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(primary_pattern, j->first, display_name,
content_setting, source, incognito));
}
}
// 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 == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
auto& policy_exceptions = all_provider_exceptions
[HostContentSettingsMap::GetProviderTypeFromSource(kPolicyProviderId)];
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(site_settings::kSetting, setting);
if (provider != "default")
object->SetString(site_settings::kSource, provider);
}
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 == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA);
PrefService* prefs = Profile::FromWebUI(web_ui)->GetPrefs();
const base::ListValue* policy_urls = prefs->GetList(
type == CONTENT_SETTINGS_TYPE_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) {
std::string url;
bool valid_string = entry->GetAsString(&url);
if (!valid_string)
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 = GetDisplayName(pattern, extension_registry);
exceptions->push_back(GetExceptionForPage(
pattern, ContentSettingsPattern(), display_name, CONTENT_SETTING_ALLOW,
kPolicyProviderId, 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.
std::unique_ptr<base::DictionaryValue> GetChooserExceptionForPage(
const GURL& requesting_origin,
const GURL& embedding_origin,
const std::string& provider_name,
bool incognito,
const std::string& name,
const base::DictionaryValue* object) {
std::unique_ptr<base::DictionaryValue> exception(new base::DictionaryValue());
std::string setting_string =
content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT);
DCHECK(!setting_string.empty());
exception->SetString(site_settings::kSetting, setting_string);
exception->SetString(site_settings::kOrigin, requesting_origin.spec());
exception->SetString(site_settings::kDisplayName, requesting_origin.spec());
exception->SetString(
site_settings::kEmbeddingOrigin, embedding_origin.spec());
exception->SetString(site_settings::kSource, provider_name);
exception->SetBoolean(kIncognito, incognito);
if (object) {
exception->SetString(kObjectName, name);
exception->Set(kObject, object->CreateDeepCopy());
}
return exception;
}
void GetChooserExceptionsFromProfile(
Profile* profile,
bool incognito,
const ChooserTypeNameEntry& chooser_type,
base::ListValue* exceptions) {
if (incognito) {
if (!profile->HasOffTheRecordProfile())
return;
profile = profile->GetOffTheRecordProfile();
}
ChooserContextBase* chooser_context = chooser_type.get_context(profile);
std::vector<std::unique_ptr<ChooserContextBase::Object>> objects =
chooser_context->GetAllGrantedObjects();
AllOriginObjects all_origin_objects;
for (const auto& object : objects) {
std::string name;
bool found = object->object.GetString(chooser_type.ui_name_key, &name);
DCHECK(found);
// It is safe for this structure to hold references into |objects| because
// they are both destroyed at the end of this function.
all_origin_objects[make_pair(object->requesting_origin,
object->source)][object->embedding_origin]
.insert(make_pair(name, &object->object));
}
// 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];
for (const auto& all_origin_objects_entry : all_origin_objects) {
const GURL& requesting_origin = all_origin_objects_entry.first.first;
const std::string& source = all_origin_objects_entry.first.second;
const OneOriginObjects& one_origin_objects =
all_origin_objects_entry.second;
auto& this_provider_exceptions = all_provider_exceptions
[HostContentSettingsMap::GetProviderTypeFromSource(source)];
// Add entries for any non-embedded origins.
bool has_embedded_entries = false;
for (const auto& one_origin_objects_entry : one_origin_objects) {
const GURL& embedding_origin = one_origin_objects_entry.first;
const SortedObjects& sorted_objects = one_origin_objects_entry.second;
// Skip the embedded settings which will be added below.
if (requesting_origin != embedding_origin) {
has_embedded_entries = true;
continue;
}
for (const auto& sorted_objects_entry : sorted_objects) {
this_provider_exceptions.push_back(GetChooserExceptionForPage(
requesting_origin, embedding_origin, source, incognito,
sorted_objects_entry.first,
sorted_objects_entry.second));
}
}
if (has_embedded_entries) {
// Add a "parent" entry that simply acts as a heading for all entries
// where |requesting_origin| has been embedded.
this_provider_exceptions.push_back(
GetChooserExceptionForPage(requesting_origin, requesting_origin,
source, incognito, std::string(),
nullptr));
// Add the "children" for any embedded settings.
for (const auto& one_origin_objects_entry : one_origin_objects) {
const GURL& embedding_origin = one_origin_objects_entry.first;
const SortedObjects& sorted_objects = one_origin_objects_entry.second;
// Skip the non-embedded setting which we already added above.
if (requesting_origin == embedding_origin)
continue;
for (const auto& sorted_objects_entry : sorted_objects) {
this_provider_exceptions.push_back(GetChooserExceptionForPage(
requesting_origin, embedding_origin, source, incognito,
sorted_objects_entry.first, sorted_objects_entry.second));
}
}
}
}
for (auto& one_provider_exceptions : all_provider_exceptions) {
for (auto& exception : one_provider_exceptions)
exceptions->Append(std::move(exception));
}
}
} // namespace site_settings