blob: 455ddd33af76532de178371ac6b34f5a352298a2 [file] [log] [blame]
// Copyright (c) 2013 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/common/extensions/permissions/permission_set.h"
#include <algorithm>
#include <iterator>
#include <string>
#include "base/stl_util.h"
#include "chrome/common/extensions/permissions/chrome_scheme_hosts.h"
#include "chrome/common/extensions/permissions/media_galleries_permission.h"
#include "chrome/common/extensions/permissions/permissions_info.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
#include "grit/generated_resources.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using extensions::URLPatternSet;
namespace {
// Helper for GetDistinctHosts(): com > net > org > everything else.
bool RcdBetterThan(const std::string& a, const std::string& b) {
if (a == b)
return false;
if (a == "com")
return true;
if (a == "net")
return b != "com";
if (a == "org")
return b != "com" && b != "net";
return false;
}
void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
DCHECK(out);
for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
URLPattern p = *i;
p.SetPath("/*");
out->AddPattern(p);
}
}
} // namespace
namespace extensions {
//
// PermissionSet
//
PermissionSet::PermissionSet() {}
PermissionSet::PermissionSet(
const APIPermissionSet& apis,
const URLPatternSet& explicit_hosts,
const URLPatternSet& scriptable_hosts)
: apis_(apis),
scriptable_hosts_(scriptable_hosts) {
AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
InitImplicitPermissions();
InitEffectiveHosts();
}
// static
PermissionSet* PermissionSet::CreateDifference(
const PermissionSet* set1,
const PermissionSet* set2) {
scoped_refptr<PermissionSet> empty = new PermissionSet();
const PermissionSet* set1_safe = (set1 == NULL) ? empty.get() : set1;
const PermissionSet* set2_safe = (set2 == NULL) ? empty.get() : set2;
APIPermissionSet apis;
APIPermissionSet::Difference(set1_safe->apis(), set2_safe->apis(), &apis);
URLPatternSet explicit_hosts;
URLPatternSet::CreateDifference(set1_safe->explicit_hosts(),
set2_safe->explicit_hosts(),
&explicit_hosts);
URLPatternSet scriptable_hosts;
URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(),
set2_safe->scriptable_hosts(),
&scriptable_hosts);
return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
}
// static
PermissionSet* PermissionSet::CreateIntersection(
const PermissionSet* set1,
const PermissionSet* set2) {
scoped_refptr<PermissionSet> empty = new PermissionSet();
const PermissionSet* set1_safe = (set1 == NULL) ? empty.get() : set1;
const PermissionSet* set2_safe = (set2 == NULL) ? empty.get() : set2;
APIPermissionSet apis;
APIPermissionSet::Intersection(set1_safe->apis(), set2_safe->apis(), &apis);
URLPatternSet explicit_hosts;
URLPatternSet::CreateIntersection(set1_safe->explicit_hosts(),
set2_safe->explicit_hosts(),
&explicit_hosts);
URLPatternSet scriptable_hosts;
URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(),
set2_safe->scriptable_hosts(),
&scriptable_hosts);
return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
}
// static
PermissionSet* PermissionSet::CreateUnion(
const PermissionSet* set1,
const PermissionSet* set2) {
scoped_refptr<PermissionSet> empty = new PermissionSet();
const PermissionSet* set1_safe = (set1 == NULL) ? empty.get() : set1;
const PermissionSet* set2_safe = (set2 == NULL) ? empty.get() : set2;
APIPermissionSet apis;
APIPermissionSet::Union(set1_safe->apis(), set2_safe->apis(), &apis);
URLPatternSet explicit_hosts;
URLPatternSet::CreateUnion(set1_safe->explicit_hosts(),
set2_safe->explicit_hosts(),
&explicit_hosts);
URLPatternSet scriptable_hosts;
URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(),
set2_safe->scriptable_hosts(),
&scriptable_hosts);
return new PermissionSet(apis, explicit_hosts, scriptable_hosts);
}
// static
PermissionSet* PermissionSet::ExcludeNotInManifestPermissions(
const PermissionSet* set) {
if (!set)
return new PermissionSet();
APIPermissionSet apis;
for (APIPermissionSet::const_iterator i = set->apis().begin();
i != set->apis().end(); ++i) {
if (!i->ManifestEntryForbidden())
apis.insert(i->Clone());
}
return new PermissionSet(
apis, set->explicit_hosts(), set->scriptable_hosts());
}
bool PermissionSet::operator==(
const PermissionSet& rhs) const {
return apis_ == rhs.apis_ &&
scriptable_hosts_ == rhs.scriptable_hosts_ &&
explicit_hosts_ == rhs.explicit_hosts_;
}
bool PermissionSet::Contains(const PermissionSet& set) const {
return apis_.Contains(set.apis()) &&
explicit_hosts().Contains(set.explicit_hosts()) &&
scriptable_hosts().Contains(set.scriptable_hosts());
}
std::set<std::string> PermissionSet::GetAPIsAsStrings() const {
std::set<std::string> apis_str;
for (APIPermissionSet::const_iterator i = apis_.begin();
i != apis_.end(); ++i) {
apis_str.insert(i->name());
}
return apis_str;
}
std::set<std::string> PermissionSet::GetDistinctHostsForDisplay() const {
URLPatternSet hosts_displayed_as_url;
// Filters out every URL pattern that matches chrome:// scheme.
for (URLPatternSet::const_iterator i = effective_hosts_.begin();
i != effective_hosts_.end(); ++i) {
if (i->scheme() != chrome::kChromeUIScheme) {
hosts_displayed_as_url.AddPattern(*i);
}
}
return GetDistinctHosts(hosts_displayed_as_url, true, true);
}
PermissionMessages PermissionSet::GetPermissionMessages(
Manifest::Type extension_type) const {
PermissionMessages messages;
if (HasEffectiveFullAccess()) {
messages.push_back(PermissionMessage(
PermissionMessage::kFullAccess,
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
return messages;
}
std::set<PermissionMessage> host_msgs =
GetHostPermissionMessages(extension_type);
std::set<PermissionMessage> api_msgs = GetAPIPermissionMessages();
messages.insert(messages.end(), host_msgs.begin(), host_msgs.end());
messages.insert(messages.end(), api_msgs.begin(), api_msgs.end());
return messages;
}
std::vector<string16> PermissionSet::GetWarningMessages(
Manifest::Type extension_type) const {
std::vector<string16> messages;
PermissionMessages permissions = GetPermissionMessages(extension_type);
bool audio_capture = false;
bool video_capture = false;
bool media_galleries_read = false;
bool media_galleries_copy_to = false;
for (PermissionMessages::const_iterator i = permissions.begin();
i != permissions.end(); ++i) {
switch (i->id()) {
case PermissionMessage::kAudioCapture:
audio_capture = true;
break;
case PermissionMessage::kVideoCapture:
video_capture = true;
break;
case PermissionMessage::kMediaGalleriesAllGalleriesRead:
media_galleries_read = true;
break;
case PermissionMessage::kMediaGalleriesAllGalleriesCopyTo:
media_galleries_copy_to = true;
break;
default:
break;
}
}
for (PermissionMessages::const_iterator i = permissions.begin();
i != permissions.end(); ++i) {
int id = i->id();
if (audio_capture && video_capture) {
if (id == PermissionMessage::kAudioCapture) {
messages.push_back(l10n_util::GetStringUTF16(
IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE));
continue;
} else if (id == PermissionMessage::kVideoCapture) {
// The combined message will be pushed above.
continue;
}
}
if (media_galleries_read && media_galleries_copy_to) {
if (id == PermissionMessage::kMediaGalleriesAllGalleriesRead) {
messages.push_back(l10n_util::GetStringUTF16(
IDS_EXTENSION_PROMPT_WARNING_MEDIA_GALLERIES_READ_WRITE));
continue;
} else if (id == PermissionMessage::kMediaGalleriesAllGalleriesCopyTo) {
// The combined message will be pushed above.
continue;
}
}
// The warning message for declarativeWebRequest permissions speaks about
// blocking parts of pages, which is a subset of what the "<all_urls>"
// access allows. Therefore we display only the "<all_urls>" warning message
// if both permissions are required.
if (id == PermissionMessage::kDeclarativeWebRequest &&
HasEffectiveAccessToAllHosts())
continue;
messages.push_back(i->message());
}
return messages;
}
std::vector<string16> PermissionSet::GetWarningMessagesDetails(
Manifest::Type extension_type) const {
std::vector<string16> messages;
PermissionMessages permissions = GetPermissionMessages(extension_type);
for (PermissionMessages::const_iterator i = permissions.begin();
i != permissions.end(); ++i)
messages.push_back(i->details());
return messages;
}
bool PermissionSet::IsEmpty() const {
// Not default if any host permissions are present.
if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
return false;
// Or if it has no api permissions.
return apis().empty();
}
bool PermissionSet::HasAPIPermission(
APIPermission::ID id) const {
return apis().find(id) != apis().end();
}
bool PermissionSet::HasAPIPermission(const std::string& permission_name) const {
const APIPermissionInfo* permission =
PermissionsInfo::GetInstance()->GetByName(permission_name);
CHECK(permission) << permission_name;
return (permission && apis_.count(permission->id()));
}
bool PermissionSet::CheckAPIPermission(APIPermission::ID permission) const {
return CheckAPIPermissionWithParam(permission, NULL);
}
bool PermissionSet::CheckAPIPermissionWithParam(
APIPermission::ID permission,
const APIPermission::CheckParam* param) const {
APIPermissionSet::const_iterator iter = apis().find(permission);
if (iter == apis().end())
return false;
return iter->Check(param);
}
bool PermissionSet::HasExplicitAccessToOrigin(
const GURL& origin) const {
return explicit_hosts().MatchesURL(origin);
}
bool PermissionSet::HasScriptableAccessToURL(
const GURL& origin) const {
// We only need to check our host list to verify access. The host list should
// already reflect any special rules (such as chrome://favicon, all hosts
// access, etc.).
return scriptable_hosts().MatchesURL(origin);
}
bool PermissionSet::HasEffectiveAccessToAllHosts() const {
// There are two ways this set can have effective access to all hosts:
// 1) it has an <all_urls> URL pattern.
// 2) it has a named permission with implied full URL access.
for (URLPatternSet::const_iterator host = effective_hosts().begin();
host != effective_hosts().end(); ++host) {
if (host->match_all_urls() ||
(host->match_subdomains() && host->host().empty()))
return true;
}
for (APIPermissionSet::const_iterator i = apis().begin();
i != apis().end(); ++i) {
if (i->info()->implies_full_url_access())
return true;
}
return false;
}
bool PermissionSet::HasEffectiveAccessToURL(const GURL& url) const {
return effective_hosts().MatchesURL(url);
}
bool PermissionSet::HasEffectiveFullAccess() const {
for (APIPermissionSet::const_iterator i = apis().begin();
i != apis().end(); ++i) {
if (i->info()->implies_full_access())
return true;
}
return false;
}
bool PermissionSet::HasLessPrivilegesThan(
const PermissionSet* permissions,
Manifest::Type extension_type) const {
// Things can't get worse than native code access.
if (HasEffectiveFullAccess())
return false;
// Otherwise, it's a privilege increase if the new one has full access.
if (permissions->HasEffectiveFullAccess())
return true;
if (HasLessHostPrivilegesThan(permissions, extension_type))
return true;
if (HasLessAPIPrivilegesThan(permissions))
return true;
return false;
}
PermissionSet::~PermissionSet() {}
// static
std::set<std::string> PermissionSet::GetDistinctHosts(
const URLPatternSet& host_patterns,
bool include_rcd,
bool exclude_file_scheme) {
// Use a vector to preserve order (also faster than a map on small sets).
// Each item is a host split into two parts: host without RCDs and
// current best RCD.
typedef std::vector<std::pair<std::string, std::string> > HostVector;
HostVector hosts_best_rcd;
for (URLPatternSet::const_iterator i = host_patterns.begin();
i != host_patterns.end(); ++i) {
if (exclude_file_scheme && i->scheme() == chrome::kFileScheme)
continue;
std::string host = i->host();
// Add the subdomain wildcard back to the host, if necessary.
if (i->match_subdomains())
host = "*." + host;
// If the host has an RCD, split it off so we can detect duplicates.
std::string rcd;
size_t reg_len = net::registry_controlled_domains::GetRegistryLength(
host,
net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
if (reg_len && reg_len != std::string::npos) {
if (include_rcd) // else leave rcd empty
rcd = host.substr(host.size() - reg_len);
host = host.substr(0, host.size() - reg_len);
}
// Check if we've already seen this host.
HostVector::iterator it = hosts_best_rcd.begin();
for (; it != hosts_best_rcd.end(); ++it) {
if (it->first == host)
break;
}
// If this host was found, replace the RCD if this one is better.
if (it != hosts_best_rcd.end()) {
if (include_rcd && RcdBetterThan(rcd, it->second))
it->second = rcd;
} else { // Previously unseen host, append it.
hosts_best_rcd.push_back(std::make_pair(host, rcd));
}
}
// Build up the final vector by concatenating hosts and RCDs.
std::set<std::string> distinct_hosts;
for (HostVector::iterator it = hosts_best_rcd.begin();
it != hosts_best_rcd.end(); ++it)
distinct_hosts.insert(it->first + it->second);
return distinct_hosts;
}
void PermissionSet::InitImplicitPermissions() {
// The downloads permission implies the internal version as well.
if (apis_.find(APIPermission::kDownloads) != apis_.end())
apis_.insert(APIPermission::kDownloadsInternal);
// TODO(fsamuel): Is there a better way to request access to the WebRequest
// API without exposing it to the Chrome App?
if (apis_.find(APIPermission::kWebView) != apis_.end())
apis_.insert(APIPermission::kWebRequestInternal);
// The webRequest permission implies the internal version as well.
if (apis_.find(APIPermission::kWebRequest) != apis_.end())
apis_.insert(APIPermission::kWebRequestInternal);
// The fileBrowserHandler permission implies the internal version as well.
if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end())
apis_.insert(APIPermission::kFileBrowserHandlerInternal);
}
void PermissionSet::InitEffectiveHosts() {
effective_hosts_.ClearPatterns();
URLPatternSet::CreateUnion(
explicit_hosts(), scriptable_hosts(), &effective_hosts_);
}
std::set<PermissionMessage> PermissionSet::GetAPIPermissionMessages() const {
std::set<PermissionMessage> messages;
for (APIPermissionSet::const_iterator permission_it = apis_.begin();
permission_it != apis_.end(); ++permission_it) {
if (permission_it->HasMessages()) {
PermissionMessages new_messages = permission_it->GetMessages();
messages.insert(new_messages.begin(), new_messages.end());
}
}
// A special hack: If kFileSystemWriteDirectory would be displayed, hide
// kFileSystemDirectory and and kFileSystemWrite as the write directory
// message implies the other two.
// TODO(sammc): Remove this. See http://crbug.com/284849.
std::set<PermissionMessage>::iterator write_directory_message =
messages.find(PermissionMessage(
PermissionMessage::kFileSystemWriteDirectory, string16()));
if (write_directory_message != messages.end()) {
messages.erase(
PermissionMessage(PermissionMessage::kFileSystemWrite, string16()));
messages.erase(
PermissionMessage(PermissionMessage::kFileSystemDirectory, string16()));
}
return messages;
}
std::set<PermissionMessage> PermissionSet::GetHostPermissionMessages(
Manifest::Type extension_type) const {
// Since platform apps always use isolated storage, they can't (silently)
// access user data on other domains, so there's no need to prompt.
// Note: this must remain consistent with HasLessHostPrivilegesThan.
// See crbug.com/255229.
std::set<PermissionMessage> messages;
if (extension_type == Manifest::TYPE_PLATFORM_APP)
return messages;
if (HasEffectiveAccessToAllHosts()) {
messages.insert(PermissionMessage(
PermissionMessage::kHostsAll,
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
} else {
PermissionMessages additional_warnings =
GetChromeSchemePermissionWarnings(effective_hosts_);
for (size_t i = 0; i < additional_warnings.size(); ++i)
messages.insert(additional_warnings[i]);
std::set<std::string> hosts = GetDistinctHostsForDisplay();
if (!hosts.empty())
messages.insert(PermissionMessage::CreateFromHostList(hosts));
}
return messages;
}
bool PermissionSet::HasLessAPIPrivilegesThan(
const PermissionSet* permissions) const {
if (permissions == NULL)
return false;
typedef std::set<PermissionMessage> PermissionMsgSet;
PermissionMsgSet current_warnings = GetAPIPermissionMessages();
PermissionMsgSet new_warnings = permissions->GetAPIPermissionMessages();
PermissionMsgSet delta_warnings =
base::STLSetDifference<PermissionMsgSet>(new_warnings, current_warnings);
// A special hack: the DWR permission is weaker than all hosts permission.
if (delta_warnings.size() == 1u &&
delta_warnings.begin()->id() ==
PermissionMessage::kDeclarativeWebRequest &&
HasEffectiveAccessToAllHosts()) {
return false;
}
// A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory and
// kFileSystemWrite.
// TODO(sammc): Remove this. See http://crbug.com/284849.
if (current_warnings.find(PermissionMessage(
PermissionMessage::kFileSystemWriteDirectory, string16())) !=
current_warnings.end()) {
delta_warnings.erase(
PermissionMessage(PermissionMessage::kFileSystemDirectory, string16()));
delta_warnings.erase(
PermissionMessage(PermissionMessage::kFileSystemWrite, string16()));
}
// We have less privileges if there are additional warnings present.
return !delta_warnings.empty();
}
bool PermissionSet::HasLessHostPrivilegesThan(
const PermissionSet* permissions,
Manifest::Type extension_type) const {
// Platform apps host permission changes do not count as privilege increases.
// Note: this must remain consistent with GetHostPermissionMessages.
if (extension_type == Manifest::TYPE_PLATFORM_APP)
return false;
// If this permission set can access any host, then it can't be elevated.
if (HasEffectiveAccessToAllHosts())
return false;
// Likewise, if the other permission set has full host access, then it must be
// a privilege increase.
if (permissions->HasEffectiveAccessToAllHosts())
return true;
const URLPatternSet& old_list = effective_hosts();
const URLPatternSet& new_list = permissions->effective_hosts();
// TODO(jstritar): This is overly conservative with respect to subdomains.
// For example, going from *.google.com to www.google.com will be
// considered an elevation, even though it is not (http://crbug.com/65337).
std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false));
std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false));
std::set<std::string> new_hosts_only =
base::STLSetDifference<std::set<std::string> >(new_hosts_set,
old_hosts_set);
return !new_hosts_only.empty();
}
} // namespace extensions