diff options
author | mek <mek@chromium.org> | 2014-09-08 16:55:57 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-09 00:28:25 +0000 |
commit | 7e1d745456f66b1d32df236e285a849c8d69db00 (patch) | |
tree | d0fddd508d27323418619bd5ee039c6f518f15a8 /chrome | |
parent | 1ce8e56ea17a7ec0af1683f3aa98ef73ea82d63f (diff) | |
download | chromium_src-7e1d745456f66b1d32df236e285a849c8d69db00.zip chromium_src-7e1d745456f66b1d32df236e285a849c8d69db00.tar.gz chromium_src-7e1d745456f66b1d32df236e285a849c8d69db00.tar.bz2 |
Respect the clipboardRead and clipboardWrite permissions in content scripts.
Added an extra "effective extension" property to ScriptContext for this to still work correctly in about:blank iframes inside extension pages.
BUG=395376
Review URL: https://codereview.chromium.org/498513002
Cr-Commit-Position: refs/heads/master@{#293818}
Diffstat (limited to 'chrome')
15 files changed, 320 insertions, 101 deletions
diff --git a/chrome/browser/renderer_host/chrome_extension_message_filter.cc b/chrome/browser/renderer_host/chrome_extension_message_filter.cc index ae53191..69f919c 100644 --- a/chrome/browser/renderer_host/chrome_extension_message_filter.cc +++ b/chrome/browser/renderer_host/chrome_extension_message_filter.cc @@ -23,13 +23,11 @@ #include "content/public/browser/render_process_host.h" #include "extensions/browser/extension_system.h" #include "extensions/common/api/messaging/message.h" -#include "extensions/common/constants.h" #include "extensions/common/extension_messages.h" #include "extensions/common/file_util.h" #include "extensions/common/message_bundle.h" using content::BrowserThread; -using extensions::APIPermission; namespace { @@ -88,10 +86,6 @@ bool ChromeExtensionMessageFilter::OnMessageReceived( const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ChromeExtensionMessageFilter, message) - IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CanTriggerClipboardRead, - OnCanTriggerClipboardRead) - IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CanTriggerClipboardWrite, - OnCanTriggerClipboardWrite) IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToExtension, OnOpenChannelToExtension) IPC_MESSAGE_HANDLER(ExtensionHostMsg_OpenChannelToTab, OnOpenChannelToTab) @@ -133,21 +127,6 @@ void ChromeExtensionMessageFilter::OnDestruct() const { } } -void ChromeExtensionMessageFilter::OnCanTriggerClipboardRead( - const GURL& origin, bool* allowed) { - *allowed = extension_info_map_->SecurityOriginHasAPIPermission( - origin, render_process_id_, APIPermission::kClipboardRead); -} - -void ChromeExtensionMessageFilter::OnCanTriggerClipboardWrite( - const GURL& origin, bool* allowed) { - // Since all extensions could historically write to the clipboard, preserve it - // for compatibility. - *allowed = (origin.SchemeIs(extensions::kExtensionScheme) || - extension_info_map_->SecurityOriginHasAPIPermission( - origin, render_process_id_, APIPermission::kClipboardWrite)); -} - void ChromeExtensionMessageFilter::OnOpenChannelToExtension( int routing_id, const ExtensionMsg_ExternalConnectionInfo& info, diff --git a/chrome/browser/renderer_host/chrome_extension_message_filter.h b/chrome/browser/renderer_host/chrome_extension_message_filter.h index 781afea..62722d2 100644 --- a/chrome/browser/renderer_host/chrome_extension_message_filter.h +++ b/chrome/browser/renderer_host/chrome_extension_message_filter.h @@ -47,9 +47,6 @@ class ChromeExtensionMessageFilter : public content::BrowserMessageFilter, virtual ~ChromeExtensionMessageFilter(); - void OnCanTriggerClipboardRead(const GURL& origin, bool* allowed); - void OnCanTriggerClipboardWrite(const GURL& origin, bool* allowed); - // TODO(jamescook): Move these functions into the extensions module. Ideally // this would be in extensions::ExtensionMessageFilter but that will require // resolving the MessageService and ActivityLog dependencies on src/chrome. diff --git a/chrome/common/extensions/chrome_extension_messages.h b/chrome/common/extensions/chrome_extension_messages.h index 764d31a..d6414d5 100644 --- a/chrome/common/extensions/chrome_extension_messages.h +++ b/chrome/common/extensions/chrome_extension_messages.h @@ -71,14 +71,6 @@ IPC_MESSAGE_ROUTED4(ExtensionMsg_InlineWebstoreInstallResponse, // Messages sent from the renderer to the browser. -// Sent by the renderer to check if a URL has permission to trigger a clipboard -// read/write operation from the DOM. -IPC_SYNC_MESSAGE_CONTROL1_1(ChromeViewHostMsg_CanTriggerClipboardRead, - GURL /* origin */, - bool /* allowed */) -IPC_SYNC_MESSAGE_CONTROL1_1(ChromeViewHostMsg_CanTriggerClipboardWrite, - GURL /* origin */, - bool /* allowed */) // Sent by the renderer to implement chrome.webstore.install(). IPC_MESSAGE_ROUTED5(ExtensionHostMsg_InlineWebstoreInstall, diff --git a/chrome/renderer/content_settings_observer.cc b/chrome/renderer/content_settings_observer.cc index 57de075..8c9d8c6 100644 --- a/chrome/renderer/content_settings_observer.cc +++ b/chrome/renderer/content_settings_observer.cc @@ -26,6 +26,8 @@ #include "chrome/common/extensions/chrome_extension_messages.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" +#include "extensions/common/permissions/api_permission.h" +#include "extensions/common/permissions/permissions_data.h" #include "extensions/renderer/dispatcher.h" #endif @@ -410,10 +412,15 @@ bool ContentSettingsObserver::allowStorage(bool local) { bool ContentSettingsObserver::allowReadFromClipboard(bool default_value) { bool allowed = false; #if defined(ENABLE_EXTENSIONS) - WebFrame* frame = render_frame()->GetWebFrame(); - // TODO(dcheng): Should we consider a toURL() method on WebSecurityOrigin? - Send(new ChromeViewHostMsg_CanTriggerClipboardRead( - GURL(frame->document().securityOrigin().toString()), &allowed)); + extensions::ScriptContext* calling_context = + extension_dispatcher_->script_context_set().GetCalling(); + if (calling_context) { + const extensions::Extension* extension = + calling_context->effective_extension(); + allowed = extension && + extension->permissions_data()->HasAPIPermission( + extensions::APIPermission::kClipboardRead); + } #endif return allowed; } @@ -421,9 +428,22 @@ bool ContentSettingsObserver::allowReadFromClipboard(bool default_value) { bool ContentSettingsObserver::allowWriteToClipboard(bool default_value) { bool allowed = false; #if defined(ENABLE_EXTENSIONS) - WebFrame* frame = render_frame()->GetWebFrame(); - Send(new ChromeViewHostMsg_CanTriggerClipboardWrite( - GURL(frame->document().securityOrigin().toString()), &allowed)); + // All blessed extension pages could historically write to the clipboard, so + // preserve that for compatibility. + extensions::ScriptContext* calling_context = + extension_dispatcher_->script_context_set().GetCalling(); + if (calling_context) { + if (calling_context->effective_context_type() == + extensions::Feature::BLESSED_EXTENSION_CONTEXT) { + allowed = true; + } else { + const extensions::Extension* extension = + calling_context->effective_extension(); + allowed = extension && + extension->permissions_data()->HasAPIPermission( + extensions::APIPermission::kClipboardWrite); + } + } #endif return allowed; } diff --git a/chrome/renderer/content_settings_observer.h b/chrome/renderer/content_settings_observer.h index 1c79500..8b08ba8 100644 --- a/chrome/renderer/content_settings_observer.h +++ b/chrome/renderer/content_settings_observer.h @@ -111,7 +111,6 @@ class ContentSettingsObserver // Otherwise returns NULL. const extensions::Extension* GetExtension( const blink::WebSecurityOrigin& origin) const; - #endif // Helpers. diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc index 3b7bbbd..c01edba 100644 --- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc +++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc @@ -63,9 +63,16 @@ ChromeExtensionsDispatcherDelegate::CreateScriptContext( const v8::Handle<v8::Context>& v8_context, blink::WebFrame* frame, const extensions::Extension* extension, - extensions::Feature::Context context_type) { - return scoped_ptr<extensions::ScriptContext>(new extensions::ChromeV8Context( - v8_context, frame, extension, context_type)); + extensions::Feature::Context context_type, + const extensions::Extension* effective_extension, + extensions::Feature::Context effective_context_type) { + return scoped_ptr<extensions::ScriptContext>( + new extensions::ChromeV8Context(v8_context, + frame, + extension, + context_type, + effective_extension, + effective_context_type)); } void ChromeExtensionsDispatcherDelegate::InitOriginPermissions( diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h index 89be8f4..0c0799f 100644 --- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h +++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.h @@ -21,7 +21,9 @@ class ChromeExtensionsDispatcherDelegate const v8::Handle<v8::Context>& v8_context, blink::WebFrame* frame, const extensions::Extension* extension, - extensions::Feature::Context context_type) OVERRIDE; + extensions::Feature::Context context_type, + const extensions::Extension* effective_extension, + extensions::Feature::Context effective_context_type) OVERRIDE; virtual void InitOriginPermissions(const extensions::Extension* extension, bool is_extension_active) OVERRIDE; virtual void RegisterNativeHandlers( diff --git a/chrome/renderer/extensions/chrome_v8_context.cc b/chrome/renderer/extensions/chrome_v8_context.cc index 40764e4..a893161 100644 --- a/chrome/renderer/extensions/chrome_v8_context.cc +++ b/chrome/renderer/extensions/chrome_v8_context.cc @@ -9,8 +9,15 @@ namespace extensions { ChromeV8Context::ChromeV8Context(const v8::Handle<v8::Context>& v8_context, blink::WebFrame* web_frame, const Extension* extension, - Feature::Context context_type) - : ScriptContext(v8_context, web_frame, extension, context_type) { + Feature::Context context_type, + const Extension* effective_extension, + Feature::Context effective_context_type) + : ScriptContext(v8_context, + web_frame, + extension, + context_type, + effective_extension, + effective_context_type) { } } // namespace extensions diff --git a/chrome/renderer/extensions/chrome_v8_context.h b/chrome/renderer/extensions/chrome_v8_context.h index 1b74453..49539cc 100644 --- a/chrome/renderer/extensions/chrome_v8_context.h +++ b/chrome/renderer/extensions/chrome_v8_context.h @@ -34,7 +34,9 @@ class ChromeV8Context : public ScriptContext { ChromeV8Context(const v8::Handle<v8::Context>& context, blink::WebFrame* frame, const Extension* extension, - Feature::Context context_type); + Feature::Context context_type, + const Extension* effective_extension, + Feature::Context effective_context_type); private: DISALLOW_COPY_AND_ASSIGN(ChromeV8Context); diff --git a/chrome/test/data/extensions/api_test/clipboard/extension/content_script.js b/chrome/test/data/extensions/api_test/clipboard/extension/content_script.js new file mode 100644 index 0000000..27a4b5b --- /dev/null +++ b/chrome/test/data/extensions/api_test/clipboard/extension/content_script.js @@ -0,0 +1,34 @@ +// Copyright 2014 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. + +// TODO(kalman): Consolidate this test script with the other clipboard tests. + +function appendTextarea() { + return document.body.appendChild(document.createElement('textarea')); +} + +function run() { + var textIn = appendTextarea(); + + textIn.focus(); + textIn.value = 'foobar'; + textIn.selectionStart = 0; + textIn.selectionEnd = 'foobar'.length; + if (!document.execCommand('copy')) + return 'Failed to copy'; + + var textOut = appendTextarea(); + + textOut.focus(); + if (!document.execCommand('paste')) + return 'Failed to paste'; + if (textOut.value != 'foobar') + return 'Expected "foobar", got ' + textOut.value; + + return ''; +} + +chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { + sendResponse(run()); +}); diff --git a/chrome/test/data/extensions/api_test/clipboard/extension/manifest.json b/chrome/test/data/extensions/api_test/clipboard/extension/manifest.json index f65eb23..102eb45 100644 --- a/chrome/test/data/extensions/api_test/clipboard/extension/manifest.json +++ b/chrome/test/data/extensions/api_test/clipboard/extension/manifest.json @@ -6,5 +6,11 @@ "background": { "page": "test.html" }, - "permissions": ["clipboardRead"] + "permissions": ["clipboardRead", "clipboardWrite", "http://localhost/*"], + "content_scripts": [ + { + "matches": ["http://*/*test_file_with_body.html"], + "js": ["content_script.js"] + } + ] } diff --git a/chrome/test/data/extensions/api_test/clipboard/extension/test.js b/chrome/test/data/extensions/api_test/clipboard/extension/test.js index 3ee936e..bac3b89 100644 --- a/chrome/test/data/extensions/api_test/clipboard/extension/test.js +++ b/chrome/test/data/extensions/api_test/clipboard/extension/test.js @@ -5,33 +5,35 @@ // Clipboard permission test for Chrome. // browser_tests.exe --gtest_filter=ClipboardApiTest.Extension -chrome.test.runTests([ - function domCopy() { - if (document.execCommand('copy')) - chrome.test.succeed(); - else - chrome.test.fail('execCommand("copy") failed'); - }, - function domPaste() { - if (document.execCommand('paste')) - chrome.test.succeed(); - else - chrome.test.fail('execCommand("paste") failed'); - }, - function copyInIframe() { - var ifr = document.createElement('iframe'); - document.body.appendChild(ifr); - window.command = 'copy'; - ifr.contentDocument.write('<script src="iframe.js"></script>'); - }, - function pasteInIframe() { - var ifr = document.createElement('iframe'); - document.body.appendChild(ifr); - window.command = 'paste'; - ifr.contentDocument.write('<script src="iframe.js"></script>'); - } -]); +// TODO(kalman): Consolidate this test script with the other clipboard tests. + +function testDomCopy() { + if (document.execCommand('copy')) + chrome.test.succeed(); + else + chrome.test.fail('execCommand("copy") failed'); +} + +function testDomPaste() { + if (document.execCommand('paste')) + chrome.test.succeed(); + else + chrome.test.fail('execCommand("paste") failed'); +} +function testCopyInIframe() { + var ifr = document.createElement('iframe'); + document.body.appendChild(ifr); + window.command = 'copy'; + ifr.contentDocument.write('<script src="iframe.js"></script>'); +} + +function testPasteInIframe() { + var ifr = document.createElement('iframe'); + document.body.appendChild(ifr); + window.command = 'paste'; + ifr.contentDocument.write('<script src="iframe.js"></script>'); +} function testDone(result) { if (result) @@ -39,3 +41,66 @@ function testDone(result) { else chrome.test.fail(); } + +function testExecuteScriptCopyPaste(baseUrl) { + var tabUrl = baseUrl + '/test_file.html'; + function runScript(tabId) { + chrome.tabs.executeScript(tabId, {file: 'content_script.js'}, + chrome.test.callbackPass(function() { + chrome.tabs.sendMessage(tabId, "run", + chrome.test.callbackPass(function(result) { + chrome.tabs.remove(tabId); + chrome.test.assertEq('', result); + })); + })); + } + + chrome.tabs.create({url: tabUrl}, chrome.test.callbackPass(function(newTab) { + var done = chrome.test.listenForever(chrome.tabs.onUpdated, + function(_, info, updatedTab) { + if (updatedTab.id == newTab.id && info.status == 'complete') { + runScript(newTab.id); + done(); + } + }); + })); +} + +function testContentScriptCopyPaste(baseUrl) { + var tabUrl = baseUrl + '/test_file_with_body.html'; + function runScript(tabId) { + chrome.tabs.sendMessage(tabId, "run", + chrome.test.callbackPass(function(result) { + chrome.tabs.remove(tabId); + chrome.test.assertEq('', result); + })); + } + + chrome.tabs.create({url: tabUrl}, chrome.test.callbackPass(function(newTab) { + var done = chrome.test.listenForever(chrome.tabs.onUpdated, + function(_, info, updatedTab) { + if (updatedTab.id == newTab.id && info.status == 'complete') { + runScript(newTab.id); + done(); + } + }); + })); +} + +function bindTest(test, param) { + var result = test.bind(null, param); + result.generatedName = test.name; + return result; +} + +chrome.test.getConfig(function(config) { + var baseUrl = 'http://localhost:' + config.testServer.port + '/extensions'; + chrome.test.runTests([ + testDomCopy, + testDomPaste, + testCopyInIframe, + testPasteInIframe, + bindTest(testExecuteScriptCopyPaste, baseUrl), + bindTest(testContentScriptCopyPaste, baseUrl) + ]); +}) diff --git a/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/content_script.js b/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/content_script.js new file mode 100644 index 0000000..3094241 --- /dev/null +++ b/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/content_script.js @@ -0,0 +1,34 @@ +// Copyright 2014 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. + +// TODO(kalman): Consolidate this test script with the other clipboard tests. + +function appendTextarea() { + return document.body.appendChild(document.createElement('textarea')); +} + +function run() { + var textIn = appendTextarea(); + + textIn.focus(); + textIn.value = 'foobar'; + textIn.selectionStart = 0; + textIn.selectionEnd = 'foobar'.length; + if (document.execCommand('copy')) + return 'Succeeded to copy'; + + var textOut = appendTextarea(); + + textOut.focus(); + if (document.execCommand('paste')) + return 'Succeeded to paste'; + if (textOut.value == 'foobar') + return 'Successfully copied/pasted despite execCommand failures'; + + return ''; +} + +chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { + sendResponse(run()); +}); diff --git a/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/manifest.json b/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/manifest.json index 58a5d31..1ce8cf9 100644 --- a/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/manifest.json +++ b/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/manifest.json @@ -5,5 +5,12 @@ "description": "end-to-end browser test for clipboard permissions", "background": { "page": "test.html" - } + }, + "permissions": ["http://localhost/*"], + "content_scripts": [ + { + "matches": ["http://*/*test_file_with_body.html"], + "js": ["content_script.js"] + } + ] } diff --git a/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/test.js b/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/test.js index 2264f07..45d38f7 100644 --- a/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/test.js +++ b/chrome/test/data/extensions/api_test/clipboard/extension_no_permission/test.js @@ -5,32 +5,37 @@ // Clipboard permission test for Chrome. // browser_tests.exe --gtest_filter=ClipboardApiTest.ExtensionNoPermission -chrome.test.runTests([ - function domCopy() { - if (document.execCommand('copy')) - chrome.test.succeed(); - else - chrome.test.fail('execCommand("copy") failed'); - }, - function domPaste() { - if (!document.execCommand('paste')) - chrome.test.succeed(); - else - chrome.test.fail('execCommand("paste") succeeded'); - }, - function copyInIframe() { - var ifr = document.createElement('iframe'); - document.body.appendChild(ifr); - window.command = 'copy'; - ifr.contentDocument.write('<script src="iframe.js"></script>'); - }, - function pasteInIframe() { - var ifr = document.createElement('iframe'); - document.body.appendChild(ifr); - window.command = 'paste'; - ifr.contentDocument.write('<script src="iframe.js"></script>'); - } -]); +// TODO(kalman): Consolidate this test script with the other clipboard tests. + +var pass = chrome.test.callbackPass; + +function testDomCopy() { + if (document.execCommand('copy')) + chrome.test.succeed(); + else + chrome.test.fail('execCommand("copy") failed'); +} + +function testDomPaste() { + if (document.execCommand('paste')) + chrome.test.fail('execCommand("paste") succeeded'); + else + chrome.test.succeed(); +} + +function testCopyInIframe() { + var ifr = document.createElement('iframe'); + document.body.appendChild(ifr); + window.command = 'copy'; + ifr.contentDocument.write('<script src="iframe.js"></script>'); +} + +function testPasteInIframe() { + var ifr = document.createElement('iframe'); + document.body.appendChild(ifr); + window.command = 'paste'; + ifr.contentDocument.write('<script src="iframe.js"></script>'); +} function testDone(result) { // 'copy' should always succeed regardless of the clipboardWrite permission, @@ -42,3 +47,66 @@ function testDone(result) { else chrome.test.fail(); } + +function testExecuteScriptCopyPaste(baseUrl) { + var tabUrl = baseUrl + '/test_file.html'; + function runScript(tabId) { + chrome.tabs.executeScript(tabId, {file: 'content_script.js'}, + chrome.test.callbackPass(function() { + chrome.tabs.sendMessage(tabId, "run", + chrome.test.callbackPass(function(result) { + chrome.tabs.remove(tabId); + chrome.test.assertEq('', result); + })); + })); + } + + chrome.tabs.create({url: tabUrl}, pass(function(newTab) { + var done = chrome.test.listenForever(chrome.tabs.onUpdated, + function(_, info, updatedTab) { + if (updatedTab.id == newTab.id && info.status == 'complete') { + runScript(newTab.id); + done(); + } + }); + })); +} + +function testContentScriptCopyPaste(baseUrl) { + var tabUrl = baseUrl + '/test_file_with_body.html'; + function runScript(tabId) { + chrome.tabs.sendMessage(tabId, "run", + chrome.test.callbackPass(function(result) { + chrome.tabs.remove(tabId); + chrome.test.assertEq('', result); + })); + } + + chrome.tabs.create({url: tabUrl}, chrome.test.callbackPass(function(newTab) { + var done = chrome.test.listenForever(chrome.tabs.onUpdated, + function(_, info, updatedTab) { + if (updatedTab.id == newTab.id && info.status == 'complete') { + runScript(newTab.id); + done(); + } + }); + })); +} + +function bindTest(test, param) { + var result = test.bind(null, param); + result.generatedName = test.name; + return result; +} + +chrome.test.getConfig(function(config) { + var baseUrl = 'http://localhost:' + config.testServer.port + '/extensions'; + chrome.test.runTests([ + testDomCopy, + testDomPaste, + testCopyInIframe, + testPasteInIframe, + bindTest(testExecuteScriptCopyPaste, baseUrl), + bindTest(testContentScriptCopyPaste, baseUrl) + ]); +}); |