blob: e7d5cf1f8aa4cd35d2ce10f94640e40891c7cd19 [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 "extensions/common/permissions/permissions_data.h"
#include <utility>
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/stl_util.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/permissions/permission_message_provider.h"
#include "extensions/common/switches.h"
#include "extensions/common/url_pattern_set.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
namespace extensions {
namespace {
PermissionsData::PolicyDelegate* g_policy_delegate = nullptr;
struct DefaultPolicyRestrictions {
URLPatternSet blocked_hosts;
URLPatternSet allowed_hosts;
};
// URLs an extension can't interact with. An extension can override these
// settings by declaring its own list of blocked and allowed hosts using
// policy_blocked_hosts and policy_allowed_hosts.
base::LazyInstance<DefaultPolicyRestrictions>::Leaky
default_policy_restrictions = LAZY_INSTANCE_INITIALIZER;
class AutoLockOnValidThread {
public:
AutoLockOnValidThread(base::Lock& lock, base::ThreadChecker* thread_checker)
: auto_lock_(lock) {
DCHECK(!thread_checker || thread_checker->CalledOnValidThread());
}
private:
base::AutoLock auto_lock_;
DISALLOW_COPY_AND_ASSIGN(AutoLockOnValidThread);
};
} // namespace
PermissionsData::PermissionsData(
const ExtensionId& extension_id,
Manifest::Type manifest_type,
Manifest::Location location,
std::unique_ptr<const PermissionSet> initial_permissions)
: extension_id_(extension_id),
manifest_type_(manifest_type),
location_(location),
active_permissions_unsafe_(std::move(initial_permissions)),
withheld_permissions_unsafe_(std::make_unique<PermissionSet>()) {}
PermissionsData::~PermissionsData() {
}
// static
void PermissionsData::SetPolicyDelegate(PolicyDelegate* delegate) {
g_policy_delegate = delegate;
}
// static
bool PermissionsData::CanExecuteScriptEverywhere(
const ExtensionId& extension_id,
Manifest::Location location) {
if (location == Manifest::COMPONENT)
return true;
const ExtensionsClient::ScriptingWhitelist& whitelist =
ExtensionsClient::Get()->GetScriptingWhitelist();
return base::ContainsValue(whitelist, extension_id);
}
bool PermissionsData::IsRestrictedUrl(const GURL& document_url,
std::string* error) const {
if (CanExecuteScriptEverywhere(extension_id_, location_))
return false;
if (g_policy_delegate &&
g_policy_delegate->IsRestrictedUrl(document_url, error)) {
return true;
}
// Check if the scheme is valid for extensions. If not, return.
if (!URLPattern::IsValidSchemeForExtensions(document_url.scheme()) &&
document_url.spec() != url::kAboutBlankURL) {
if (error) {
if (active_permissions().HasAPIPermission(APIPermission::kTab)) {
*error = ErrorUtils::FormatErrorMessage(
manifest_errors::kCannotAccessPageWithUrl, document_url.spec());
} else {
*error = manifest_errors::kCannotAccessPage;
}
}
return true;
}
if (!ExtensionsClient::Get()->IsScriptableURL(document_url, error))
return true;
bool allow_on_chrome_urls = base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kExtensionsOnChromeURLs);
if (document_url.SchemeIs(content::kChromeUIScheme) &&
!allow_on_chrome_urls) {
if (error)
*error = manifest_errors::kCannotAccessChromeUrl;
return true;
}
if (document_url.SchemeIs(kExtensionScheme) &&
document_url.host() != extension_id_ && !allow_on_chrome_urls) {
if (error)
*error = manifest_errors::kCannotAccessExtensionUrl;
return true;
}
return false;
}
// static
bool PermissionsData::AllUrlsIncludesChromeUrls(
const std::string& extension_id) {
return extension_id == extension_misc::kChromeVoxExtensionId;
}
bool PermissionsData::UsesDefaultPolicyHostRestrictions() const {
DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
return uses_default_policy_host_restrictions;
}
const URLPatternSet& PermissionsData::default_policy_blocked_hosts() {
return default_policy_restrictions.Get().blocked_hosts;
}
const URLPatternSet& PermissionsData::default_policy_allowed_hosts() {
return default_policy_restrictions.Get().allowed_hosts;
}
const URLPatternSet PermissionsData::policy_blocked_hosts() const {
base::AutoLock auto_lock(runtime_lock_);
return PolicyBlockedHostsUnsafe().Clone();
}
const URLPatternSet& PermissionsData::PolicyBlockedHostsUnsafe() const {
runtime_lock_.AssertAcquired();
if (uses_default_policy_host_restrictions)
return default_policy_blocked_hosts();
return policy_blocked_hosts_unsafe_;
}
const URLPatternSet PermissionsData::policy_allowed_hosts() const {
base::AutoLock auto_lock(runtime_lock_);
return PolicyAllowedHostsUnsafe().Clone();
}
const URLPatternSet& PermissionsData::PolicyAllowedHostsUnsafe() const {
runtime_lock_.AssertAcquired();
if (uses_default_policy_host_restrictions)
return default_policy_allowed_hosts();
return policy_allowed_hosts_unsafe_;
}
void PermissionsData::BindToCurrentThread() const {
DCHECK(!thread_checker_);
thread_checker_.reset(new base::ThreadChecker());
}
void PermissionsData::SetPermissions(
std::unique_ptr<const PermissionSet> active,
std::unique_ptr<const PermissionSet> withheld) const {
AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
active_permissions_unsafe_ = std::move(active);
withheld_permissions_unsafe_ = std::move(withheld);
}
void PermissionsData::SetPolicyHostRestrictions(
const URLPatternSet& policy_blocked_hosts,
const URLPatternSet& policy_allowed_hosts) const {
AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
policy_blocked_hosts_unsafe_ = policy_blocked_hosts.Clone();
policy_allowed_hosts_unsafe_ = policy_allowed_hosts.Clone();
uses_default_policy_host_restrictions = false;
}
void PermissionsData::SetUsesDefaultHostRestrictions() const {
AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
uses_default_policy_host_restrictions = true;
}
// static
void PermissionsData::SetDefaultPolicyHostRestrictions(
const URLPatternSet& default_policy_blocked_hosts,
const URLPatternSet& default_policy_allowed_hosts) {
default_policy_restrictions.Get().blocked_hosts =
default_policy_blocked_hosts.Clone();
default_policy_restrictions.Get().allowed_hosts =
default_policy_allowed_hosts.Clone();
}
void PermissionsData::SetActivePermissions(
std::unique_ptr<const PermissionSet> active) const {
AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
active_permissions_unsafe_ = std::move(active);
}
void PermissionsData::UpdateTabSpecificPermissions(
int tab_id,
const PermissionSet& permissions) const {
AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
CHECK_GE(tab_id, 0);
TabPermissionsMap::const_iterator iter =
tab_specific_permissions_.find(tab_id);
std::unique_ptr<const PermissionSet> new_permissions =
PermissionSet::CreateUnion(
iter == tab_specific_permissions_.end()
? static_cast<const PermissionSet&>(PermissionSet())
: *iter->second,
permissions);
tab_specific_permissions_[tab_id] = std::move(new_permissions);
}
void PermissionsData::ClearTabSpecificPermissions(int tab_id) const {
AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
CHECK_GE(tab_id, 0);
tab_specific_permissions_.erase(tab_id);
}
bool PermissionsData::HasAPIPermission(APIPermission::ID permission) const {
base::AutoLock auto_lock(runtime_lock_);
return active_permissions_unsafe_->HasAPIPermission(permission);
}
bool PermissionsData::HasAPIPermission(
const std::string& permission_name) const {
base::AutoLock auto_lock(runtime_lock_);
return active_permissions_unsafe_->HasAPIPermission(permission_name);
}
bool PermissionsData::HasAPIPermissionForTab(
int tab_id,
APIPermission::ID permission) const {
base::AutoLock auto_lock(runtime_lock_);
if (active_permissions_unsafe_->HasAPIPermission(permission))
return true;
const PermissionSet* tab_permissions = GetTabSpecificPermissions(tab_id);
return tab_permissions && tab_permissions->HasAPIPermission(permission);
}
bool PermissionsData::CheckAPIPermissionWithParam(
APIPermission::ID permission,
const APIPermission::CheckParam* param) const {
base::AutoLock auto_lock(runtime_lock_);
return active_permissions_unsafe_->CheckAPIPermissionWithParam(permission,
param);
}
URLPatternSet PermissionsData::GetEffectiveHostPermissions() const {
base::AutoLock auto_lock(runtime_lock_);
URLPatternSet effective_hosts =
active_permissions_unsafe_->effective_hosts().Clone();
for (const auto& val : tab_specific_permissions_)
effective_hosts.AddPatterns(val.second->effective_hosts());
return effective_hosts;
}
bool PermissionsData::HasHostPermission(const GURL& url) const {
base::AutoLock auto_lock(runtime_lock_);
return active_permissions_unsafe_->HasExplicitAccessToOrigin(url) &&
!IsPolicyBlockedHostUnsafe(url);
}
bool PermissionsData::HasEffectiveAccessToAllHosts() const {
base::AutoLock auto_lock(runtime_lock_);
return active_permissions_unsafe_->HasEffectiveAccessToAllHosts();
}
PermissionMessages PermissionsData::GetPermissionMessages() const {
base::AutoLock auto_lock(runtime_lock_);
return PermissionMessageProvider::Get()->GetPermissionMessages(
PermissionMessageProvider::Get()->GetAllPermissionIDs(
*active_permissions_unsafe_, manifest_type_));
}
PermissionMessages PermissionsData::GetNewPermissionMessages(
const PermissionSet& granted_permissions) const {
base::AutoLock auto_lock(runtime_lock_);
std::unique_ptr<const PermissionSet> new_permissions =
PermissionSet::CreateDifference(*active_permissions_unsafe_,
granted_permissions);
return PermissionMessageProvider::Get()->GetPermissionMessages(
PermissionMessageProvider::Get()->GetAllPermissionIDs(*new_permissions,
manifest_type_));
}
bool PermissionsData::CanAccessPage(const GURL& document_url,
int tab_id,
std::string* error) const {
PageAccess result = GetPageAccess(document_url, tab_id, error);
// TODO(rdevlin.cronin) Update callers so that they only need
// PageAccess::kAllowed.
return result == PageAccess::kAllowed || result == PageAccess::kWithheld;
}
PermissionsData::PageAccess PermissionsData::GetPageAccess(
const GURL& document_url,
int tab_id,
std::string* error) const {
base::AutoLock auto_lock(runtime_lock_);
const PermissionSet* tab_permissions = GetTabSpecificPermissions(tab_id);
return CanRunOnPage(
document_url, tab_id, active_permissions_unsafe_->explicit_hosts(),
withheld_permissions_unsafe_->explicit_hosts(),
tab_permissions ? &tab_permissions->explicit_hosts() : nullptr, error);
}
bool PermissionsData::CanRunContentScriptOnPage(const GURL& document_url,
int tab_id,
std::string* error) const {
PageAccess result = GetContentScriptAccess(document_url, tab_id, error);
// TODO(rdevlin.cronin) Update callers so that they only need
// PageAccess::kAllowed.
return result == PageAccess::kAllowed || result == PageAccess::kWithheld;
}
PermissionsData::PageAccess PermissionsData::GetContentScriptAccess(
const GURL& document_url,
int tab_id,
std::string* error) const {
base::AutoLock auto_lock(runtime_lock_);
const PermissionSet* tab_permissions = GetTabSpecificPermissions(tab_id);
return CanRunOnPage(
document_url, tab_id, active_permissions_unsafe_->scriptable_hosts(),
withheld_permissions_unsafe_->scriptable_hosts(),
tab_permissions ? &tab_permissions->scriptable_hosts() : nullptr, error);
}
bool PermissionsData::CanCaptureVisiblePage(
const GURL& document_url,
int tab_id,
std::string* error,
CaptureRequirement capture_requirement) const {
bool has_active_tab = false;
bool has_all_urls = false;
bool has_page_capture = false;
// Check the real origin, in order to account for filesystem:, blob:, etc.
// (url::Origin grabs the inner origin of these, whereas GURL::GetOrigin()
// does not.)
url::Origin origin = url::Origin::Create(document_url);
const GURL origin_url = origin.GetURL();
{
base::AutoLock auto_lock(runtime_lock_);
// Disallow capturing policy-blocked hosts. No exceptions.
// Note: This isn't foolproof, since an extension could embed a policy-
// blocked host in a different page and then capture that, but it's better
// than nothing (and policy hosts can set their x-frame options
// accordingly).
if (location_ != Manifest::COMPONENT &&
IsPolicyBlockedHostUnsafe(origin_url)) {
if (error)
*error = extension_misc::kPolicyBlockedScripting;
return false;
}
const PermissionSet* tab_permissions = GetTabSpecificPermissions(tab_id);
has_active_tab = tab_permissions &&
tab_permissions->HasAPIPermission(APIPermission::kTab);
// Check if any of the host permissions match all urls. We don't use
// URLPatternSet::ContainsPattern() here because a) the schemes may be
// different and b) this is more efficient.
for (const auto& pattern : active_permissions_unsafe_->explicit_hosts()) {
if (pattern.match_all_urls()) {
has_all_urls = true;
break;
}
}
has_page_capture = active_permissions_unsafe_->HasAPIPermission(
APIPermission::kPageCapture);
}
std::string access_error;
if (capture_requirement == CaptureRequirement::kActiveTabOrAllUrls) {
if (!has_active_tab && !has_all_urls) {
if (error)
*error = manifest_errors::kAllURLOrActiveTabNeeded;
return false;
}
// We check GetPageAccess() (in addition to the <all_urls> and activeTab
// checks below) for the case of URLs that can be conditionally granted
// (such as file:// URLs or chrome:// URLs for component extensions). If an
// extension has <all_urls>, GetPageAccess() will still (correctly) return
// false if, for instance, the URL is a file:// URL and the extension does
// not have file access. See https://crbug.com/810220. If the extension has
// page access (and has activeTab or <all_urls>), allow the capture.
if (GetPageAccess(origin_url, tab_id, &access_error) ==
PageAccess::kAllowed)
return true;
} else {
DCHECK_EQ(CaptureRequirement::kPageCapture, capture_requirement);
if (!has_page_capture) {
if (error)
*error = manifest_errors::kPageCaptureNeeded;
}
// If the URL is a typical web URL, the pageCapture permission is
// sufficient.
if ((origin_url.SchemeIs(url::kHttpScheme) ||
origin_url.SchemeIs(url::kHttpsScheme)) &&
!origin.IsSameOriginWith(url::Origin::Create(
ExtensionsClient::Get()->GetWebstoreBaseURL()))) {
return true;
}
}
// The extension doesn't have explicit page access. However, there are a
// number of cases where tab capture may still be allowed.
// First special case: an extension's own pages.
// These aren't restricted URLs, but won't be matched by <all_urls> or
// activeTab (since the extension scheme is not included in the list of
// valid schemes for extension permissions). To capture an extension's own
// page, either activeTab or <all_urls> is needed (it's no higher privilege
// than a normal web page). At least one of these is still needed because
// the extension page may have embedded web content.
// TODO(devlin): Should activeTab/<all_urls> account for the extension's own
// domain?
if (origin_url.host() == extension_id_)
return true;
// The following are special cases that require activeTab explicitly. Normal
// extensions will never have full access to these pages (i.e., can never
// inject scripts or otherwise modify the page), but capturing the page can
// still be useful for e.g. screenshots. We allow these pages only if the
// extension has been explicitly granted activeTab, which serves as a
// stronger guarantee that the user wants to run the extension on the site.
// These origins include:
// - chrome:-scheme pages.
// - Other extension's pages.
// - data: URLs (which don't have a defined underlying origin).
// - The Chrome Web Store.
bool allowed_with_active_tab =
origin_url.SchemeIs(content::kChromeUIScheme) ||
origin_url.SchemeIs(kExtensionScheme) ||
// Note: The origin of a data: url is empty, so check the url itself.
document_url.SchemeIs(url::kDataScheme) ||
origin.IsSameOriginWith(
url::Origin::Create(ExtensionsClient::Get()->GetWebstoreBaseURL()));
if (!allowed_with_active_tab) {
if (error)
*error = access_error;
return false;
}
// If the extension has activeTab, these origins are allowed.
if (has_active_tab)
return true;
// Otherwise, access is denied.
if (error)
*error = manifest_errors::kActiveTabPermissionNotGranted;
return false;
}
const PermissionSet* PermissionsData::GetTabSpecificPermissions(
int tab_id) const {
runtime_lock_.AssertAcquired();
TabPermissionsMap::const_iterator iter =
tab_specific_permissions_.find(tab_id);
return iter != tab_specific_permissions_.end() ? iter->second.get() : nullptr;
}
bool PermissionsData::IsPolicyBlockedHostUnsafe(const GURL& url) const {
runtime_lock_.AssertAcquired();
return PolicyBlockedHostsUnsafe().MatchesURL(url) &&
!PolicyAllowedHostsUnsafe().MatchesURL(url);
}
PermissionsData::PageAccess PermissionsData::CanRunOnPage(
const GURL& document_url,
int tab_id,
const URLPatternSet& permitted_url_patterns,
const URLPatternSet& withheld_url_patterns,
const URLPatternSet* tab_url_patterns,
std::string* error) const {
runtime_lock_.AssertAcquired();
if (location_ != Manifest::COMPONENT &&
IsPolicyBlockedHostUnsafe(document_url)) {
if (error)
*error = extension_misc::kPolicyBlockedScripting;
return PageAccess::kDenied;
}
if (IsRestrictedUrl(document_url, error))
return PageAccess::kDenied;
if (tab_url_patterns && tab_url_patterns->MatchesURL(document_url))
return PageAccess::kAllowed;
if (permitted_url_patterns.MatchesURL(document_url))
return PageAccess::kAllowed;
if (withheld_url_patterns.MatchesURL(document_url))
return PageAccess::kWithheld;
if (error) {
if (active_permissions_unsafe_->HasAPIPermission(APIPermission::kTab)) {
*error = ErrorUtils::FormatErrorMessage(
manifest_errors::kCannotAccessPageWithUrl, document_url.spec());
} else {
*error = manifest_errors::kCannotAccessPage;
}
}
return PageAccess::kDenied;
}
} // namespace extensions