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
[email protected]
Review URL: https://chromiumcodereview.appspot.com/10815028
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149631 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/active_tab_apitest.cc b/chrome/browser/extensions/active_tab_apitest.cc
new file mode 100644
index 0000000..0470aa20
--- /dev/null
+++ b/chrome/browser/extensions/active_tab_apitest.cc
@@ -0,0 +1,51 @@
+// 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
diff --git a/chrome/browser/extensions/active_tab_permission_manager.cc b/chrome/browser/extensions/active_tab_permission_manager.cc
index d591e1f..67d6d58 100644
--- a/chrome/browser/extensions/active_tab_permission_manager.cc
+++ b/chrome/browser/extensions/active_tab_permission_manager.cc
@@ -49,19 +49,18 @@
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 @@
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());
}
diff --git a/chrome/browser/extensions/active_tab_unittest.cc b/chrome/browser/extensions/active_tab_unittest.cc
index 1fbb4b4..35e5e48 100644
--- a/chrome/browser/extensions/active_tab_unittest.cc
+++ b/chrome/browser/extensions/active_tab_unittest.cc
@@ -84,8 +84,9 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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) {
diff --git a/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc b/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc
index 699eb903..1fe587ea 100644
--- a/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc
+++ b/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.cc
@@ -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 (details_->code.get())
+ return Execute(*details_->code);
- if (script_info->HasKey(keys::kRunAtKey)) {
- std::string run_string;
- EXTENSION_FUNCTION_VALIDATE(script_info->GetString(
- keys::kRunAtKey, &run_string));
+ CHECK(details_->file.get());
+ resource_ = GetExtension()->GetResource(*details_->file);
- 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 (!code_string.empty())
- return Execute(code_string);
-
- 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 @@
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 @@
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;
diff --git a/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.h b/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.h
index 7a1f519..fa500e1 100644
--- a/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.h
+++ b/chrome/browser/extensions/api/tabs/execute_code_in_tab_function.h
@@ -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 @@
virtual ~ExecuteCodeInTabFunction();
// ExtensionFunction:
+ virtual bool HasPermission() OVERRIDE;
virtual bool RunImpl() OVERRIDE;
// Message handler.
@@ -29,6 +38,10 @@
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 @@
// 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 {
diff --git a/chrome/browser/extensions/extension_function.cc b/chrome/browser/extensions/extension_function.cc
index 4e53d1b0..e28e353 100644
--- a/chrome/browser/extensions/extension_function.cc
+++ b/chrome/browser/extensions/extension_function.cc
@@ -83,6 +83,10 @@
return NULL;
}
+bool ExtensionFunction::HasPermission() {
+ return extension_->HasAPIPermission(name_);
+}
+
void ExtensionFunction::OnQuotaExceeded() {
error_ = QuotaLimitHeuristic::kGenericOverQuotaError;
SendResponse(false);
diff --git a/chrome/browser/extensions/extension_function.h b/chrome/browser/extensions/extension_function.h
index 0698d3bcb..adef96a26 100644
--- a/chrome/browser/extensions/extension_function.h
+++ b/chrome/browser/extensions/extension_function.h
@@ -44,12 +44,16 @@
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 @@
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
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 2e2935d..565c1248 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -136,7 +136,7 @@
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 @@
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 @@
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 @@
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 @@
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(¶ms.arguments);
@@ -283,6 +276,20 @@
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;
}
diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h
index bb719592..af2a060e 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.h
+++ b/chrome/browser/extensions/extension_function_dispatcher.h
@@ -120,6 +120,7 @@
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