Commit 3d0e2262 authored by kalman@chromium.org's avatar kalman@chromium.org

Make ActiveTabPermissionManager also grant the tabs permission for

that tab; only respected for tabs.executeScript and tabs.insertCSS.

This involved converting tabs.executeScript/insertCSS to use JSON
schema compiler.


BUG=137643
TBR=ben@chromium.org

Review URL: https://chromiumcodereview.appspot.com/10815028

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149631 0039d316-1c4b-4281-b951-d872f2087c98
parent a96b697a
// Copyright (c) 2012 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 "base/logging.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/test/base/ui_test_utils.h"
namespace extensions {
namespace {
IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ActiveTab) {
ASSERT_TRUE(StartTestServer());
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("active_tab"));
ASSERT_TRUE(extension);
ExtensionService* service =
ExtensionSystem::Get(browser()->profile())->extension_service();
// Shouldn't be initially granted based on activeTab.
{
ResultCatcher catcher;
ui_test_utils::NavigateToURL(browser(), test_server()->GetURL("page.html"));
EXPECT_TRUE(catcher.GetNextResult()) << message_;
}
// Granting to the extension should give it access to page.html.
{
ResultCatcher catcher;
service->toolbar_model()->ExecuteBrowserAction(extension, browser(), NULL);
EXPECT_TRUE(catcher.GetNextResult()) << message_;
}
// Changing page should go back to it not having access.
{
ResultCatcher catcher;
ui_test_utils::NavigateToURL(browser(),
test_server()->GetURL("final_page.html"));
EXPECT_TRUE(catcher.GetNextResult()) << message_;
}
}
} // namespace
} // namespace extensions
......@@ -49,19 +49,18 @@ void ActiveTabPermissionManager::GrantIfRequested(const Extension* extension) {
return;
}
URLPatternSet new_permissions;
const URLPatternSet* old_permissions =
extension->GetTabSpecificHostPermissions(tab_id_);
if (old_permissions)
new_permissions.AddPatterns(*old_permissions);
APIPermissionSet new_apis;
new_apis.insert(APIPermission::kTab);
URLPatternSet new_hosts;
new_hosts.AddPattern(pattern);
new_permissions.AddPattern(pattern);
extension->UpdateTabSpecificPermissions(
tab_id_, new PermissionSet(new_apis, new_hosts, URLPatternSet()));
granted_extensions_.Insert(extension);
extension->SetTabSpecificHostPermissions(tab_id_, new_permissions);
Send(new ExtensionMsg_UpdateTabSpecificPermissions(GetPageID(),
tab_id_,
extension->id(),
new_permissions));
new_hosts));
}
bool ActiveTabPermissionManager::IsGranted(const Extension* extension) {
......@@ -102,7 +101,7 @@ void ActiveTabPermissionManager::ClearActiveExtensionsAndNotify() {
for (ExtensionSet::const_iterator it = granted_extensions_.begin();
it != granted_extensions_.end(); ++it) {
(*it)->ClearTabSpecificHostPermissions(tab_id_);
(*it)->ClearTabSpecificPermissions(tab_id_);
extension_ids.push_back((*it)->id());
}
......
......@@ -84,8 +84,9 @@ class ActiveTabTest : public TabContentsTestHarness {
bool IsAllowed(const scoped_refptr<const Extension>& extension,
const GURL& url,
int tab_id) {
return (extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) &&
extension->CanCaptureVisiblePage(url, tab_id, NULL));
return extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) &&
extension->CanCaptureVisiblePage(url, tab_id, NULL) &&
HasTabsPermission(extension, tab_id);
}
bool IsBlocked(const scoped_refptr<const Extension>& extension,
......@@ -96,8 +97,18 @@ class ActiveTabTest : public TabContentsTestHarness {
bool IsBlocked(const scoped_refptr<const Extension>& extension,
const GURL& url,
int tab_id) {
return (!extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) &&
!extension->CanCaptureVisiblePage(url, tab_id, NULL));
// Note: can't check HasTabsPermission because it isn't URL specific.
return !extension->CanExecuteScriptOnPage(url, tab_id, NULL, NULL) &&
!extension->CanCaptureVisiblePage(url, tab_id, NULL);
}
bool HasTabsPermission(const scoped_refptr<const Extension>& extension) {
return HasTabsPermission(extension, tab_id());
}
bool HasTabsPermission(const scoped_refptr<const Extension>& extension,
int tab_id) {
return extension->HasAPIPermissionForTab(tab_id, APIPermission::kTab);
}
// An extension with the activeTab permission.
......@@ -122,6 +133,10 @@ TEST_F(ActiveTabTest, GrantToSinglePage) {
EXPECT_TRUE(IsBlocked(another_extension, google));
EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
EXPECT_FALSE(HasTabsPermission(extension));
EXPECT_FALSE(HasTabsPermission(another_extension));
EXPECT_FALSE(HasTabsPermission(extension_without_active_tab));
active_tab_permission_manager()->GrantIfRequested(extension);
active_tab_permission_manager()->GrantIfRequested(
extension_without_active_tab);
......@@ -135,8 +150,8 @@ TEST_F(ActiveTabTest, GrantToSinglePage) {
// Other subdomains shouldn't be given access.
GURL mail_google("http://mail.google.com");
EXPECT_TRUE(IsBlocked(extension, mail_google));
EXPECT_TRUE(IsBlocked(another_extension, google));
EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
EXPECT_TRUE(IsBlocked(another_extension, mail_google));
EXPECT_TRUE(IsBlocked(extension_without_active_tab, mail_google));
// Reloading the page should clear the active permissions.
Reload();
......@@ -145,6 +160,10 @@ TEST_F(ActiveTabTest, GrantToSinglePage) {
EXPECT_TRUE(IsBlocked(another_extension, google));
EXPECT_TRUE(IsBlocked(extension_without_active_tab, google));
EXPECT_FALSE(HasTabsPermission(extension));
EXPECT_FALSE(HasTabsPermission(another_extension));
EXPECT_FALSE(HasTabsPermission(extension_without_active_tab));
// But they should still be able to be granted again.
active_tab_permission_manager()->GrantIfRequested(extension);
......@@ -179,6 +198,10 @@ TEST_F(ActiveTabTest, GrantToSinglePage) {
EXPECT_TRUE(IsBlocked(another_extension, chromium));
EXPECT_TRUE(IsBlocked(extension_without_active_tab, chromium));
EXPECT_FALSE(HasTabsPermission(extension));
EXPECT_FALSE(HasTabsPermission(another_extension));
EXPECT_FALSE(HasTabsPermission(extension_without_active_tab));
// Should be able to grant to multiple extensions at the same time (if they
// have the activeTab permission, of course).
active_tab_permission_manager()->GrantIfRequested(extension);
......@@ -250,6 +273,7 @@ TEST_F(ActiveTabTest, OnlyActiveTab) {
EXPECT_TRUE(IsAllowed(extension, google, tab_id()));
EXPECT_TRUE(IsBlocked(extension, google, tab_id() + 1));
EXPECT_FALSE(HasTabsPermission(extension, tab_id() + 1));
}
TEST_F(ActiveTabTest, NavigateInPage) {
......
......@@ -17,6 +17,7 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/extensions/api/tabs.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_error_utils.h"
......@@ -29,106 +30,64 @@
#include "content/public/browser/web_contents.h"
using content::BrowserThread;
using extensions::api::tabs::InjectDetails;
using extensions::ScriptExecutor;
using extensions::UserScript;
namespace keys = extensions::tabs_constants;
ExecuteCodeInTabFunction::ExecuteCodeInTabFunction()
: execute_tab_id_(-1),
all_frames_(false),
run_at_(extensions::UserScript::DOCUMENT_IDLE) {
: execute_tab_id_(-1) {
}
ExecuteCodeInTabFunction::~ExecuteCodeInTabFunction() {}
bool ExecuteCodeInTabFunction::HasPermission() {
if (Init() &&
extension_->HasAPIPermissionForTab(execute_tab_id_,
extensions::APIPermission::kTab)) {
return true;
}
return ExtensionFunction::HasPermission();
}
bool ExecuteCodeInTabFunction::RunImpl() {
DictionaryValue* script_info;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &script_info));
size_t number_of_value = script_info->size();
if (number_of_value == 0) {
EXTENSION_FUNCTION_VALIDATE(Init());
if (!details_->code.get() && !details_->file.get()) {
error_ = keys::kNoCodeOrFileToExecuteError;
return false;
} else {
bool has_code = script_info->HasKey(keys::kCodeKey);
bool has_file = script_info->HasKey(keys::kFileKey);
if (has_code && has_file) {
error_ = keys::kMoreThanOneValuesError;
return false;
} else if (!has_code && !has_file) {
error_ = keys::kNoCodeOrFileToExecuteError;
return false;
}
}
if (details_->code.get() && details_->file.get()) {
error_ = keys::kMoreThanOneValuesError;
return false;
}
execute_tab_id_ = -1;
Browser* browser = NULL;
TabContents* contents = NULL;
// If |tab_id| is specified, look for it. Otherwise default to selected tab
// in the current window.
Value* tab_value = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value));
if (tab_value->IsType(Value::TYPE_NULL)) {
browser = GetCurrentBrowser();
if (!browser) {
error_ = keys::kNoCurrentWindowError;
return false;
}
if (!ExtensionTabUtil::GetDefaultTab(browser, &contents, &execute_tab_id_))
return false;
} else {
EXTENSION_FUNCTION_VALIDATE(tab_value->GetAsInteger(&execute_tab_id_));
if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(),
include_incognito(),
&browser, NULL, &contents, NULL)) {
return false;
}
// If |tab_id| is specified, look for the tab. Otherwise default to selected
// tab in the current window.
CHECK_GE(execute_tab_id_, 0);
if (!ExtensionTabUtil::GetTabById(execute_tab_id_, profile(),
include_incognito(),
NULL, NULL, &contents, NULL)) {
return false;
}
// NOTE: This can give the wrong answer due to race conditions, but it is OK,
// we check again in the renderer.
CHECK(browser);
CHECK(contents);
if (!GetExtension()->CanExecuteScriptOnPage(
contents->web_contents()->GetURL(), execute_tab_id_, NULL, &error_)) {
return false;
}
if (script_info->HasKey(keys::kAllFramesKey)) {
if (!script_info->GetBoolean(keys::kAllFramesKey, &all_frames_))
return false;
}
if (script_info->HasKey(keys::kRunAtKey)) {
std::string run_string;
EXTENSION_FUNCTION_VALIDATE(script_info->GetString(
keys::kRunAtKey, &run_string));
if (run_string == extension_manifest_values::kRunAtDocumentStart)
run_at_ = extensions::UserScript::DOCUMENT_START;
else if (run_string == extension_manifest_values::kRunAtDocumentEnd)
run_at_ = extensions::UserScript::DOCUMENT_END;
else if (run_string == extension_manifest_values::kRunAtDocumentIdle)
run_at_ = extensions::UserScript::DOCUMENT_IDLE;
else
EXTENSION_FUNCTION_VALIDATE(false);
}
std::string code_string;
if (script_info->HasKey(keys::kCodeKey)) {
if (!script_info->GetString(keys::kCodeKey, &code_string))
return false;
}
if (details_->code.get())
return Execute(*details_->code);
if (!code_string.empty())
return Execute(code_string);
CHECK(details_->file.get());
resource_ = GetExtension()->GetResource(*details_->file);
std::string relative_path;
if (script_info->HasKey(keys::kFileKey)) {
if (!script_info->GetString(keys::kFileKey, &relative_path))
return false;
resource_ = GetExtension()->GetResource(relative_path);
}
if (resource_.extension_root().empty() || resource_.relative_path().empty()) {
error_ = keys::kNoCodeOrFileToExecuteError;
return false;
......@@ -163,6 +122,38 @@ void TabsExecuteScriptFunction::OnExecuteCodeFinished(bool success,
result);
}
bool ExecuteCodeInTabFunction::Init() {
if (details_.get())
return true;
// |tab_id| is optional so it's ok if it's not there.
int tab_id = -1;
args_->GetInteger(0, &tab_id);
// |details| are not optional.
DictionaryValue* details_value = NULL;
if (!args_->GetDictionary(1, &details_value))
return false;
scoped_ptr<InjectDetails> details(new InjectDetails());
if (!InjectDetails::Populate(*details_value, details.get()))
return false;
// If the tab ID is -1 then it needs to be converted to the currently active
// tab's ID.
if (tab_id == -1) {
Browser* browser = GetCurrentBrowser();
if (!browser)
return false;
TabContents* tab_contents = NULL;
if (!ExtensionTabUtil::GetDefaultTab(browser, &tab_contents, &tab_id))
return false;
}
execute_tab_id_ = tab_id;
details_ = details.Pass();
return true;
}
void ExecuteCodeInTabFunction::DidLoadFile(bool success,
const std::string& data) {
std::string function_name = name();
......@@ -252,12 +243,32 @@ bool ExecuteCodeInTabFunction::Execute(const std::string& code_string) {
NOTREACHED();
}
ScriptExecutor::FrameScope frame_scope =
details_->all_frames.get() && *details_->all_frames ?
ScriptExecutor::ALL_FRAMES :
ScriptExecutor::TOP_FRAME;
UserScript::RunLocation run_at = UserScript::UNDEFINED;
switch (details_->run_at) {
case InjectDetails::RUN_AT_NONE:
case InjectDetails::RUN_AT_DOCUMENT_IDLE:
run_at = UserScript::DOCUMENT_IDLE;
break;
case InjectDetails::RUN_AT_DOCUMENT_START:
run_at = UserScript::DOCUMENT_START;
break;
case InjectDetails::RUN_AT_DOCUMENT_END:
run_at = UserScript::DOCUMENT_END;
break;
}
CHECK_NE(UserScript::UNDEFINED, run_at);
contents->extension_tab_helper()->script_executor()->ExecuteScript(
extension->id(),
script_type,
code_string,
all_frames_ ? ScriptExecutor::ALL_FRAMES : ScriptExecutor::TOP_FRAME,
run_at_,
frame_scope,
run_at,
ScriptExecutor::ISOLATED_WORLD,
base::Bind(&ExecuteCodeInTabFunction::OnExecuteCodeFinished, this));
return true;
......
......@@ -11,6 +11,14 @@
#include "chrome/common/extensions/extension_resource.h"
#include "chrome/common/extensions/user_script.h"
namespace extensions {
namespace api {
namespace tabs {
struct InjectDetails;
} // namespace tabs
} // namespace api
} // namespace extensions
// Implement API call tabs.executeScript and tabs.insertCSS.
class ExecuteCodeInTabFunction : public AsyncExtensionFunction {
public:
......@@ -20,6 +28,7 @@ class ExecuteCodeInTabFunction : public AsyncExtensionFunction {
virtual ~ExecuteCodeInTabFunction();
// ExtensionFunction:
virtual bool HasPermission() OVERRIDE;
virtual bool RunImpl() OVERRIDE;
// Message handler.
......@@ -29,6 +38,10 @@ class ExecuteCodeInTabFunction : public AsyncExtensionFunction {
const ListValue& script_result);
private:
// Initialize the |execute_tab_id_| and |details_| if they haven't already
// been. Returns whether initialization was successful.
bool Init();
// Called when contents from the file whose path is specified in JSON
// arguments has been loaded.
void DidLoadFile(bool success, const std::string& data);
......@@ -51,16 +64,12 @@ class ExecuteCodeInTabFunction : public AsyncExtensionFunction {
// Id of tab which executes code.
int execute_tab_id_;
// The injection details.
scoped_ptr<extensions::api::tabs::InjectDetails> details_;
// Contains extension resource built from path of file which is
// specified in JSON arguments.
ExtensionResource resource_;
// If all_frames_ is true, script or CSS text would be injected
// to all frames; Otherwise only injected to top main frame.
bool all_frames_;
// The intended time to run the script.
extensions::UserScript::RunLocation run_at_;
};
class TabsExecuteScriptFunction : public ExecuteCodeInTabFunction {
......
......@@ -83,6 +83,10 @@ IOThreadExtensionFunction* ExtensionFunction::AsIOThreadExtensionFunction() {
return NULL;
}
bool ExtensionFunction::HasPermission() {
return extension_->HasAPIPermission(name_);
}
void ExtensionFunction::OnQuotaExceeded() {
error_ = QuotaLimitHeuristic::kGenericOverQuotaError;
SendResponse(false);
......
......@@ -44,12 +44,16 @@ namespace extensions {
class WindowController;
}
#ifdef NDEBUG
#define EXTENSION_FUNCTION_VALIDATE(test) do { \
if (!(test)) { \
bad_message_ = true; \
return false; \
} \
} while (0)
#else // NDEBUG
#define EXTENSION_FUNCTION_VALIDATE(test) CHECK(test)
#endif // NDEBUG
#define EXTENSION_FUNCTION_ERROR(error) do { \
error_ = error; \
......@@ -79,6 +83,15 @@ class ExtensionFunction
virtual UIThreadExtensionFunction* AsUIThreadExtensionFunction();
virtual IOThreadExtensionFunction* AsIOThreadExtensionFunction();
// Returns true if the function has permission to run.
//
// The default implementation is to check the Extension's permissions against
// what this function requires to run, but some APIs may require finer
// grained control, such as tabs.executeScript being allowed for active tabs.
//
// This will be run after the function has been set up but before Run().
virtual bool HasPermission();
// Execute the API. Clients should initialize the ExtensionFunction using
// SetArgs(), set_request_id(), and the other setters before calling this
// method. Derived classes should be ready to return GetResultList() and
......
......@@ -136,7 +136,7 @@ void ExtensionFunctionDispatcher::DispatchOnIOThread(
extension_info_map->process_map(),
g_global_io_data.Get().api.get(),
profile,
ipc_sender, routing_id));
ipc_sender, NULL, routing_id));
if (!function) {
LogFailure(extension, params.name, kAccessDenied);
return;
......@@ -193,7 +193,7 @@ void ExtensionFunctionDispatcher::Dispatch(
render_view_host->GetProcess()->GetID(),
*(service->process_map()),
extensions::ExtensionAPI::GetSharedInstance(),
profile(), render_view_host,
profile(), render_view_host, render_view_host,
render_view_host->GetRoutingID()));
if (!function) {
LogFailure(extension, params.name, kAccessDenied);
......@@ -206,7 +206,6 @@ void ExtensionFunctionDispatcher::Dispatch(
NOTREACHED();
return;
}
function_ui->SetRenderViewHost(render_view_host);
function_ui->set_dispatcher(AsWeakPtr());
function_ui->set_profile(profile_);
function->set_include_incognito(service->CanCrossIncognito(extension));
......@@ -251,6 +250,7 @@ ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction(
extensions::ExtensionAPI* api,
void* profile,
IPC::Sender* ipc_sender,
RenderViewHost* render_view_host,
int routing_id) {
if (!extension) {
LOG(ERROR) << "Specified extension does not exist.";
......@@ -267,13 +267,6 @@ ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction(
return NULL;
}
if (!extension->HasAPIPermission(params.name)) {
LOG(ERROR) << "Extension " << extension->id() << " does not have "
<< "permission to function: " << params.name;
SendAccessDenied(ipc_sender, routing_id, params.request_id);
return NULL;
}
ExtensionFunction* function =
ExtensionFunctionRegistry::GetInstance()->NewFunction(params.name);
function->SetArgs(&params.arguments);
......@@ -283,6 +276,20 @@ ExtensionFunction* ExtensionFunctionDispatcher::CreateExtensionFunction(
function->set_user_gesture(params.user_gesture);
function->set_extension(extension);
function->set_profile_id(profile);
UIThreadExtensionFunction* function_ui =
function->AsUIThreadExtensionFunction();
if (function_ui) {
function_ui->SetRenderViewHost(render_view_host);
}
if (!function->HasPermission()) {
LOG(ERROR) << "Extension " << extension->id() << " does not have "
<< "permission to function: " << params.name;
SendAccessDenied(ipc_sender, routing_id, params.request_id);
return NULL;
}
return function;
}
......
......@@ -120,6 +120,7 @@ class ExtensionFunctionDispatcher
extensions::ExtensionAPI* api,
void* profile,
IPC::Sender* ipc_sender,
content::RenderViewHost* render_view_host,
int routing_id);
// Helper to send an access denied error to the requesting renderer. Can be
......
......@@ -2706,6 +2706,7 @@
'browser/download/download_test_observer.cc',
'browser/download/save_page_browsertest.cc',
'browser/errorpage_browsertest.cc',
'browser/extensions/active_tab_apitest.cc',
'browser/extensions/alert_apitest.cc',
'browser/extensions/all_urls_apitest.cc',
'browser/extensions/api/app/app_apitest.cc',
......
......@@ -25,6 +25,22 @@
"status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
"incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."}
}
},
{
"id": "InjectDetails",
"type": "object",
"description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time.",
"properties": {
"code": {"type": "string", "optional": true, "description": "JavaScript or CSS code to inject."},
"file": {"type": "string", "optional": true, "description": "JavaScript or CSS file to inject."},
"allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and will only be injected into the top frame."},
"runAt": {
"type": "string",
"optional": true,
"enum": ["document_start", "document_end", "document_idle"],
"description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
}
}
}
],
"functions": [
......@@ -600,20 +616,9 @@
"parameters": [
{"type": "integer", "name": "tabId", "optional": true, "description": "The ID of the tab in which to run the script; defaults to the active tab of the current window."},
{
"type": "object",
"$ref": "tabs.InjectDetails",
"name": "details",
"description": "Details of the script to run. Either the code or the file property must be set, but both may not be set at the same time.",
"properties": {
"code": {"type": "string", "optional": true, "description": "JavaScript code to execute."},
"file": {"type": "string", "optional": true, "description": "JavaScript file to execute."},
"allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is true, this function injects script into all frames of current page. By default, it's false and script is injected only into the top main frame."},
"runAt": {
"type": "string",
"optional": true,
"enum": ["document_start", "document_end", "document_idle"],
"description": "The soonest that the script will be injected into the tab. Defaults to \"document_idle\"."
}
}
"description": "Details of the script to run."
},
{
"type": "function",
......@@ -639,20 +644,9 @@
"parameters": [
{"type": "integer", "name": "tabId", "optional": true, "description": "The ID of the tab in which to insert the CSS; defaults to the active tab of the current window."},
{
"type": "object",
"$ref": "tabs.InjectDetails",
"name": "details",
"description": "Details of the CSS text to insert. Either the code or the file property must be set, but both may not be set at the same time.",
"properties": {
"code": {"type": "string", "optional": true, "description": "CSS code to be injected."},
"file": {"type": "string", "optional": true, "description": "CSS file to be injected."},
"allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is true, this function injects CSS text into all frames of current page. By default, it's false and CSS is injected only into the top main frame."},
"runAt": {
"type": "string",
"optional": true,
"enum": ["document_start", "document_end", "document_idle"],
"description": "The soonest that the CSS will be injected into the tab. Defaults to \"document_idle\"."
}
}
"description": "Details of the CSS text to insert."
},
{
"type": "function",
......
......@@ -3469,19 +3469,28 @@ bool Extension::CanSpecifyHostPermission(const URLPattern& pattern,
return true;
}
bool Extension::HasAPIPermission(
APIPermission::ID permission) const {
bool Extension::HasAPIPermission(APIPermission::ID permission) const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->HasAPIPermission(permission);
}
bool Extension::HasAPIPermission(
const std::string& function_name) const {
bool Extension::HasAPIPermission(const std::string& function_name) const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->
HasAccessToFunction(function_name);
}
bool Extension::HasAPIPermissionForTab(int tab_id,
APIPermission::ID permission) const {
base::AutoLock auto_lock(runtime_data_lock_);
if (runtime_data_.GetActivePermissions()->HasAPIPermission(permission))
return true;
scoped_refptr<const PermissionSet> tab_specific_permissions =
runtime_data_.GetTabSpecificPermissions(tab_id);
return tab_specific_permissions.get() &&
tab_specific_permissions->HasAPIPermission(permission);
}
const URLPatternSet& Extension::GetEffectiveHostPermissions() const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetActivePermissions()->effective_hosts();
......@@ -3579,10 +3588,10 @@ bool Extension::CanExecuteScriptOnPage(const GURL& page_url,
// If a tab ID is specified, try the tab-specific permissions.
if (tab_id >= 0) {
const URLPatternSet* tab_permissions =
runtime_data_.GetTabSpecificHostPermissions(tab_id);
if (tab_permissions &&
tab_permissions->MatchesSecurityOrigin(page_url)) {
scoped_refptr<const PermissionSet> tab_permissions =
runtime_data_.GetTabSpecificPermissions(tab_id);
if (tab_permissions.get() &&
tab_permissions->explicit_hosts().MatchesSecurityOrigin(page_url)) {
return true;
}
}
......@@ -3652,10 +3661,10 @@ bool Extension::CanCaptureVisiblePage(const GURL& page_url,
int tab_id,
std::string *error) const {
if (tab_id >= 0) {
const URLPatternSet* tab_permissions =
GetTabSpecificHostPermissions(tab_id);
if (tab_permissions &&
tab_permissions->MatchesSecurityOrigin(page_url)) {
scoped_refptr<const PermissionSet> tab_permissions =
GetTabSpecificPermissions(tab_id);
if (tab_permissions.get() &&
tab_permissions->explicit_hosts().MatchesSecurityOrigin(page_url)) {
return true;
}
}
......@@ -3814,22 +3823,22 @@ bool Extension::HasContentScriptAtURL(const GURL& url) const {
return false;
}
const URLPatternSet* Extension::GetTabSpecificHostPermissions(
scoped_refptr<const PermissionSet> Extension::GetTabSpecificPermissions(
int tab_id) const {
base::AutoLock auto_lock(runtime_data_lock_);
return runtime_data_.GetTabSpecificHostPermissions(tab_id);
return runtime_data_.GetTabSpecificPermissions(tab_id);
}
void Extension::SetTabSpecificHostPermissions(
void Extension::UpdateTabSpecificPermissions(
int tab_id,
const URLPatternSet& permissions) const {
const PermissionSet* permissions) const {
base::AutoLock auto_lock(runtime_data_lock_);
runtime_data_.SetTabSpecificHostPermissions(tab_id, permissions);
runtime_data_.UpdateTabSpecificPermissions(tab_id, permissions);
}
void Extension::ClearTabSpecificHostPermissions(int tab_id) const {
void Extension::ClearTabSpecificPermissions(int tab_id) const {
base::AutoLock auto_lock(runtime_data_lock_);
runtime_data_.ClearTabSpecificHostPermissions(tab_id);
runtime_data_.ClearTabSpecificPermissions(tab_id);
}
bool Extension::CheckPlatformAppFeatures(std::string* utf8_error) {
......@@ -3871,25 +3880,29 @@ void Extension::RuntimeData::SetActivePermissions(
active_permissions_ = active;
}
const URLPatternSet*
Extension::RuntimeData::GetTabSpecificHostPermissions(int tab_id) const {
scoped_refptr<const PermissionSet>
Extension::RuntimeData::GetTabSpecificPermissions(int tab_id) const {
CHECK_GE(tab_id, 0);
TabHostPermissionsMap::const_iterator it =
tab_specific_host_permissions_.find(tab_id);
return (it != tab_specific_host_permissions_.end()) ? it->second.get() : NULL;
TabPermissionsMap::const_iterator it = tab_specific_permissions_.find(tab_id);
return (it != tab_specific_permissions_.end()) ? it->second : NULL;
}
void Extension::RuntimeData::SetTabSpecificHostPermissions(
void Extension::RuntimeData::UpdateTabSpecificPermissions(
int tab_id,
const URLPatternSet& hosts) {
const PermissionSet* permissions) {
CHECK_GE(tab_id, 0);
tab_specific_host_permissions_[tab_id] =
make_linked_ptr(new URLPatternSet(hosts));
if (tab_specific_permissions_.count(tab_id)) {
tab_specific_permissions_[tab_id] = PermissionSet::CreateUnion(
tab_specific_permissions_[tab_id],
permissions);
} else {
tab_specific_permissions_[tab_id] = permissions;
}
}
void Extension::RuntimeData::ClearTabSpecificHostPermissions(int tab_id) {
void Extension::RuntimeData::ClearTabSpecificPermissions(int tab_id) {
CHECK_GE(tab_id, 0);
tab_specific_host_permissions_.erase(tab_id);
tab_specific_permissions_.erase(tab_id);
}
UnloadedExtensionInfo::UnloadedExtensionInfo(
......
......@@ -444,6 +444,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
bool HasAPIPermission(APIPermission::ID permission) const;
bool HasAPIPermission(const std::string& function_name) const;
bool HasAPIPermissionForTab(int tab_id, APIPermission::ID permission) const;
const URLPatternSet& GetEffectiveHostPermissions() const;
......@@ -565,17 +566,16 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
// Gets the tab-specific host permissions of |tab_id|, or NULL if there
// aren't any.
//
// This is a weak pointer. Callers should create a copy before mutating any
// tab specific permissions.
const URLPatternSet* GetTabSpecificHostPermissions(int tab_id) const;
scoped_refptr<const PermissionSet> GetTabSpecificPermissions(int tab_id)
const;
// Sets the tab-specific host permissions of |tab_id| to |permissions|.
void SetTabSpecificHostPermissions(int tab_id,
const URLPatternSet& permissions) const;
// Updates the tab-specific permissions of |tab_id| to include those from
// |permissions|.
void UpdateTabSpecificPermissions(int tab_id,
const PermissionSet* permissions) const;
// Clears the tab-specific host permissions of |tab_id|.
void ClearTabSpecificHostPermissions(int tab_id) const;
// Clears the tab-specific permissions of |tab_id|.
void ClearTabSpecificPermissions(int tab_id) const;
// Accessors:
......@@ -726,19 +726,20 @@ class Extension : public base::RefCountedThreadSafe<Extension> {
void SetActivePermissions(const PermissionSet* active);
scoped_refptr<const PermissionSet> GetActivePermissions() const;
const URLPatternSet* GetTabSpecificHostPermissions(int tab_id) const;
void SetTabSpecificHostPermissions(int tab_id,
const URLPatternSet& permissions);
void ClearTabSpecificHostPermissions(int tab_id);
scoped_refptr<const PermissionSet> GetTabSpecificPermissions(int tab_id)
const;
void UpdateTabSpecificPermissions(int tab_id,
const PermissionSet* permissions);
void ClearTabSpecificPermissions(int tab_id);
private:
friend class base::RefCountedThreadSafe<RuntimeData>;
scoped_refptr<const PermissionSet> active_permissions_;
typedef std::map<int, linked_ptr<const URLPatternSet> >
TabHostPermissionsMap;
TabHostPermissionsMap tab_specific_host_permissions_;
typedef std::map<int, scoped_refptr<const PermissionSet> >
TabPermissionsMap;
TabPermissionsMap tab_specific_permissions_;
};
// Chooses the extension ID for an extension based on a variety of criteria.
......
......@@ -257,7 +257,7 @@ IPC_MESSAGE_CONTROL4(ExtensionMsg_UpdateTabSpecificPermissions,
int32 /* page_id (only relevant for the target tab) */,
int /* tab_id */,
std::string /* extension_id */,
URLPatternSet /* host */)
URLPatternSet /* hosts */)
// Tell the renderer to clear tab-specific permissions for some extensions.
IPC_MESSAGE_CONTROL2(ExtensionMsg_ClearTabSpecificPermissions,
......
......@@ -19,6 +19,8 @@
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/extension_manifest_constants.h"
#include "chrome/common/extensions/extension_resource.h"
#include "chrome/common/extensions/permissions/permission_set.h"
#include "chrome/common/extensions/permissions/api_permission.h"
#include "chrome/common/url_constants.h"
#include "googleurl/src/gurl.h"
#include "net/base/mime_sniffer.h"
......@@ -28,7 +30,9 @@
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
using extensions::APIPermissionSet;
using extensions::Extension;
using extensions::PermissionSet;
namespace keys = extension_manifest_keys;
namespace values = extension_manifest_values;
......@@ -870,9 +874,9 @@ TEST_F(ExtensionScriptAndCaptureVisibleTest, TabSpecific) {
scoped_refptr<Extension> extension =
LoadManifestStrict("script_and_capture", "tab_specific.json");
EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(0));
EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(1));
EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(2));
EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());
EXPECT_FALSE(extension->GetTabSpecificPermissions(1).get());
EXPECT_FALSE(extension->GetTabSpecificPermissions(2).get());
std::set<GURL> no_urls;
......@@ -881,53 +885,68 @@ TEST_F(ExtensionScriptAndCaptureVisibleTest, TabSpecific) {
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
URLPatternSet allowed_hosts;
allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
http_url.spec()));
allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
http_url.spec()));
std::set<GURL> allowed_urls;
allowed_urls.insert(http_url);
// http_url_with_path() will also be allowed, because Extension should be
// considering the security origin of the URL not the URL itself, and
// http_url is in allowed_hosts.
allowed_urls.insert(http_url_with_path);
extension->SetTabSpecificHostPermissions(0, allowed_hosts);
EXPECT_EQ(allowed_hosts, *extension->GetTabSpecificHostPermissions(0));
allowed_urls.insert(http_url);
// http_url_with_path() will also be allowed, because Extension should be
// considering the security origin of the URL not the URL itself, and
// http_url is in allowed_hosts.
allowed_urls.insert(http_url_with_path);
{
scoped_refptr<PermissionSet> permissions(
new PermissionSet(APIPermissionSet(), allowed_hosts, URLPatternSet()));
extension->UpdateTabSpecificPermissions(0, permissions);
EXPECT_EQ(permissions->explicit_hosts(),
extension->GetTabSpecificPermissions(0)->explicit_hosts());
}
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
extension->ClearTabSpecificHostPermissions(0);
EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(0));
extension->ClearTabSpecificPermissions(0);
EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
std::set<GURL> more_allowed_urls = allowed_urls;
more_allowed_urls.insert(https_url);
more_allowed_urls.insert(https_url);
URLPatternSet more_allowed_hosts = allowed_hosts;
more_allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
https_url.spec()));
extension->SetTabSpecificHostPermissions(0, allowed_hosts);
EXPECT_EQ(allowed_hosts, *extension->GetTabSpecificHostPermissions(0));
extension->SetTabSpecificHostPermissions(1, more_allowed_hosts);
EXPECT_EQ(more_allowed_hosts, *extension->GetTabSpecificHostPermissions(1));
more_allowed_hosts.AddPattern(URLPattern(URLPattern::SCHEME_ALL,
https_url.spec()));
{
scoped_refptr<PermissionSet> permissions(
new PermissionSet(APIPermissionSet(), allowed_hosts, URLPatternSet()));
extension->UpdateTabSpecificPermissions(0, permissions);
EXPECT_EQ(permissions->explicit_hosts(),
extension->GetTabSpecificPermissions(0)->explicit_hosts());
permissions = new PermissionSet(APIPermissionSet(),
more_allowed_hosts,
URLPatternSet());
extension->UpdateTabSpecificPermissions(1, permissions);
EXPECT_EQ(permissions->explicit_hosts(),
extension->GetTabSpecificPermissions(1)->explicit_hosts());
}
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, allowed_urls, 0));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
extension->ClearTabSpecificHostPermissions(0);
EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(0));
extension->ClearTabSpecificPermissions(0);
EXPECT_FALSE(extension->GetTabSpecificPermissions(0).get());
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, more_allowed_urls, 1));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 2));
extension->ClearTabSpecificHostPermissions(1);
EXPECT_EQ(NULL, extension->GetTabSpecificHostPermissions(1));
extension->ClearTabSpecificPermissions(1);
EXPECT_FALSE(extension->GetTabSpecificPermissions(1).get());
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 0));
EXPECT_TRUE(AllowedExclusivelyOnTab(extension, no_urls, 1));
......
......@@ -70,7 +70,6 @@ class PermissionSet
// Gets the API permissions in this set, plus any that have implicit access
// (such as APIs that require no permissions, or APIs with functions that
// require no permissions).
// TODO(kalman): return scoped_ptr to avoid copying.
std::set<std::string> GetAPIsWithAnyAccessAsStrings() const;
// Returns whether this namespace has any functions which the extension has
......
......@@ -77,6 +77,7 @@ using WebKit::WebView;
using content::RenderThread;
using content::RenderView;
using extensions::APIPermission;
using extensions::APIPermissionSet;
using extensions::ApiDefinitionsNatives;
using extensions::AppWindowCustomBindings;
using extensions::ContextMenusCustomBindings;
......@@ -996,7 +997,9 @@ void ExtensionDispatcher::OnUpdateTabSpecificPermissions(
if (!extension)
return;
extension->SetTabSpecificHostPermissions(tab_id, origin_set);
extension->UpdateTabSpecificPermissions(
tab_id,
new PermissionSet(APIPermissionSet(), origin_set, URLPatternSet()));
}
void ExtensionDispatcher::OnClearTabSpecificPermissions(
......@@ -1006,7 +1009,7 @@ void ExtensionDispatcher::OnClearTabSpecificPermissions(
it != extension_ids.end(); ++it) {
const Extension* extension = extensions_.GetByID(*it);
if (extension)
extension->ClearTabSpecificHostPermissions(tab_id);
extension->ClearTabSpecificPermissions(tab_id);
}
}
......@@ -1117,7 +1120,24 @@ bool ExtensionDispatcher::CheckCurrentContextAccessToExtensionAPI(
return false;
}
if (!context->extension() ||
if (!context->extension()) {
v8::ThrowException(
v8::Exception::Error(v8::String::New("Not in an extension.")));
return false;
}
// We need to whitelist tabs.executeScript and tabs.insertCSS because they
// are granted under special circumstances with the activeTab permission
// (note that the browser checks too, so this isn't a security problem).
//
// Only the browser knows which tab this call will be sent to... sometimes we
// *could* figure it out (if the extension gives an explicit tab ID in the
// call), but the expected case will be the extension passing through -1,
// meaning the active tab, and only the browser safely knows what this is.
bool skip_permission_check = (function_name == "tabs.executeScript") ||
(function_name == "tabs.insertCSS");
if (!skip_permission_check &&
!context->extension()->HasAPIPermission(function_name)) {
static const char kMessage[] =
"You do not have permission to use '%s'. Be sure to declare"
......
......@@ -189,6 +189,13 @@
}
};
chrome.test.assertLastError = function(expectedError) {
chrome.test.assertEq(typeof(expectedError), 'string');
chrome.test.assertTrue(chrome.runtime.lastError != undefined,
"No lastError, but expected " + expectedError);
chrome.test.assertEq(expectedError, chrome.runtime.lastError.message);
}
function safeFunctionApply(func, arguments) {
try {
if (func)
......@@ -211,10 +218,7 @@
if (expectedError == null) {
chrome.test.assertNoLastError();
} else {
chrome.test.assertEq(typeof(expectedError), 'string');
chrome.test.assertTrue(chrome.runtime.lastError != undefined,
"No lastError, but expected " + expectedError);
chrome.test.assertEq(expectedError, chrome.runtime.lastError.message);
chrome.test.assertLastError(expectedError);
}
if (func) {
......
// Copyright (c) 2012 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.
var theOnlyTestDone = null;
function inject(callback) {
chrome.tabs.executeScript({ code: "true" }, callback);
}
chrome.browserAction.onClicked.addListener(function() {
inject(function() {
chrome.test.assertNoLastError();
chrome.test.notifyPass();
});
});
chrome.webNavigation.onCompleted.addListener(function(details) {
inject(function() {
chrome.test.assertLastError("Access to extension API denied.");
if (details.url.indexOf("final_page") >= 0)
theOnlyTestDone();
else
chrome.test.notifyPass();
});
});
chrome.test.runTests([
function theOnlyTest() {
// This will keep the test alive until the final callback is run.
theOnlyTestDone = chrome.test.callbackAdded();
}
]);
{
"name": "activeTab",
"description": "API test for activeTab",
"version": "0.1",
"browser_action": {
"default_title": "activeTab"
},
"permissions": [
"activeTab",
"webNavigation"
],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment