diff --git a/DEPS b/DEPS index de81f4134cf6..490c8a023f0f 100644 --- a/DEPS +++ b/DEPS @@ -5,7 +5,7 @@ deps = { "vendor/tracking-protection": "https://github.com/brave/tracking-protection.git@051177425a14121a22087d754ad8eb1c0ce8fb24", "vendor/hashset-cpp": "https://github.com/brave/hashset-cpp.git@67ffffa69b56e330bab9d08f050727f891c916a1", "vendor/bloom-filter-cpp": "https://github.com/brave/bloom-filter-cpp.git@d511cf872ea1d650ab8dc4662f6036dac012d197", - "vendor/brave-extension": "https://github.com/brave/brave-extension.git@af3a95d08868741c179707b6839e564f9d95d4dc", + "vendor/brave-extension": "https://github.com/brave/brave-extension.git@2751cf40937d1a3893f44d0d4fd32952ab4df194", "vendor/requests": "https://github.com/kennethreitz/requests@e4d59bedfd3c7f4f254f4f5d036587bcd8152458", "vendor/boto": "https://github.com/boto/boto@f7574aa6cc2c819430c1f05e9a1a1a666ef8169b", "vendor/python-patch": "https://github.com/svn2github/python-patch@a336a458016ced89aba90dfc3f4c8222ae3b1403", diff --git a/browser/extensions/BUILD.gn b/browser/extensions/BUILD.gn index 0fd2f0a2e131..e495123185ef 100644 --- a/browser/extensions/BUILD.gn +++ b/browser/extensions/BUILD.gn @@ -15,6 +15,7 @@ source_set("extensions") { ] deps = [ "//brave/browser/resources:brave_extension_grit", - "//chrome/browser" + "//chrome/browser", + "//content/public/browser", ] } diff --git a/browser/extensions/api/brave_shields_api.cc b/browser/extensions/api/brave_shields_api.cc index 5cc3ecb99259..f9cdd6245dcd 100644 --- a/browser/extensions/api/brave_shields_api.cc +++ b/browser/extensions/api/brave_shields_api.cc @@ -4,13 +4,42 @@ #include "brave/browser/extensions/api/brave_shields_api.h" +#include "brave/common/extensions/api/brave_shields.h" +#include "brave/components/brave_shields/browser/brave_shields_web_contents_observer.h" +#include "chrome/browser/extensions/api/tabs/tabs_constants.h" +#include "chrome/browser/extensions/extension_tab_util.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/web_contents.h" + +using brave_shields::BraveShieldsWebContentsObserver; + namespace extensions { namespace api { -BraveShieldsDummyFunction::~BraveShieldsDummyFunction() { +BraveShieldsAllowScriptsOnceFunction::~BraveShieldsAllowScriptsOnceFunction() { } -ExtensionFunction::ResponseAction BraveShieldsDummyFunction::Run() { +ExtensionFunction::ResponseAction BraveShieldsAllowScriptsOnceFunction::Run() { + std::unique_ptr params( + brave_shields::AllowScriptsOnce::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + // Get web contents for this tab + content::WebContents* contents = nullptr; + if (!ExtensionTabUtil::GetTabById( + params->tab_id, + Profile::FromBrowserContext(browser_context()), + include_incognito(), + nullptr, + nullptr, + &contents, + nullptr)) { + return RespondNow(Error(tabs_constants::kTabNotFoundError, + base::IntToString(params->tab_id))); + } + + BraveShieldsWebContentsObserver::FromWebContents( + contents)->AllowScriptsOnce(params->origins, contents); return RespondNow(NoArguments()); } diff --git a/browser/extensions/api/brave_shields_api.h b/browser/extensions/api/brave_shields_api.h index 7040012d6abe..71028fc0b238 100644 --- a/browser/extensions/api/brave_shields_api.h +++ b/browser/extensions/api/brave_shields_api.h @@ -2,20 +2,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef BRAVE_COMMON_EXTENSIONS_API_BRAVE_SHIELDS_API_H_ -#define BRAVE_COMMON_EXTENSIONS_API_BRAVE_SHIELDS_API_H_ +#ifndef BRAVE_BROWSER_EXTENSIONS_API_BRAVE_SHIELDS_API_H_ +#define BRAVE_BROWSER_EXTENSIONS_API_BRAVE_SHIELDS_API_H_ #include "extensions/browser/extension_function.h" namespace extensions { namespace api { -class BraveShieldsDummyFunction : public UIThreadExtensionFunction { +class BraveShieldsAllowScriptsOnceFunction : public UIThreadExtensionFunction { public: - DECLARE_EXTENSION_FUNCTION("braveShields.dummy", UNKNOWN) + DECLARE_EXTENSION_FUNCTION("braveShields.allowScriptsOnce", UNKNOWN) protected: - ~BraveShieldsDummyFunction() override; + ~BraveShieldsAllowScriptsOnceFunction() override; ResponseAction Run() override; }; @@ -23,4 +23,4 @@ class BraveShieldsDummyFunction : public UIThreadExtensionFunction { } // namespace api } // namespace extensions -#endif // BRAVE_COMMON_EXTENSIONS_API_BRAVE_SHIELDS_API_H_ +#endif // BRAVE_BROWSER_EXTENSIONS_API_BRAVE_SHIELDS_API_H_ diff --git a/browser/extensions/api/brave_shields_api_browsertest.cc b/browser/extensions/api/brave_shields_api_browsertest.cc new file mode 100644 index 000000000000..678a0df9171c --- /dev/null +++ b/browser/extensions/api/brave_shields_api_browsertest.cc @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/path_service.h" +#include "brave/browser/extensions/api/brave_shields_api.h" +#include "brave/common/brave_paths.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" +#include "chrome/browser/extensions/extension_function_test_utils.h" +#include "chrome/browser/extensions/extension_tab_util.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test_utils.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings.h" +#include "components/content_settings/core/common/content_settings_types.h" +#include "extensions/common/extension_builder.h" +#include "net/dns/mock_host_resolver.h" + +using extensions::api::BraveShieldsAllowScriptsOnceFunction; +using extension_function_test_utils::RunFunctionAndReturnError; +using extension_function_test_utils::RunFunctionAndReturnSingleResult; + +class BraveShieldsAPIBrowserTest : public InProcessBrowserTest { + public: + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + host_resolver()->AddRule("*", "127.0.0.1"); + content::SetupCrossSiteRedirector(embedded_test_server()); + + brave::RegisterPathProvider(); + base::FilePath test_data_dir; + PathService::Get(brave::DIR_TEST_DATA, &test_data_dir); + embedded_test_server()->ServeFilesFromDirectory(test_data_dir); + + ASSERT_TRUE(embedded_test_server()->Start()); + extension_ = extensions::ExtensionBuilder("Test").Build(); + } + + content::WebContents* active_contents() { + return browser()->tab_strip_model()->GetActiveWebContents(); + } + + scoped_refptr extension() { + return extension_; + } + + void BlockScripts() { + HostContentSettingsMap* content_settings = + HostContentSettingsMapFactory::GetForProfile(browser()->profile()); + content_settings->SetContentSettingCustomScope( + ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(), + CONTENT_SETTINGS_TYPE_JAVASCRIPT, "", CONTENT_SETTING_BLOCK); + } + + bool NavigateToURLUntilLoadStop( + const std::string& origin, const std::string& path) { + ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL(origin, path)); + + return WaitForLoadStop(active_contents()); + } + + private: + scoped_refptr extension_; +}; + +IN_PROC_BROWSER_TEST_F(BraveShieldsAPIBrowserTest, AllowScriptsOnce) { + BlockScripts(); + + EXPECT_TRUE( + NavigateToURLUntilLoadStop("a.com", "/load_js_from_origins.html")); + EXPECT_EQ(active_contents()->GetAllFrames().size(), 1u) << + "All script loadings should be blocked."; + + // run extension function to temporarily allow a.com + scoped_refptr function( + new BraveShieldsAllowScriptsOnceFunction()); + function->set_extension(extension().get()); + function->set_has_callback(true); + + const GURL url(embedded_test_server()->GetURL("a.com", "/simple.js")); + const std::string allow_origin = url.GetOrigin().spec(); + int tabId = extensions::ExtensionTabUtil::GetTabId(active_contents()); + + RunFunctionAndReturnSingleResult( + function.get(), + "[[\"" + allow_origin + "\"], " + std::to_string(tabId) + "]", + browser()); + + // reload page with a.com temporarily allowed + active_contents()->GetController().Reload(content::ReloadType::NORMAL, + true); + EXPECT_TRUE(WaitForLoadStop(active_contents())); + EXPECT_EQ(active_contents()->GetAllFrames().size(), 2u) << + "Scripts from a.com should be temporarily allowed."; + + // reload page again + active_contents()->GetController().Reload(content::ReloadType::NORMAL, + true); + EXPECT_TRUE(WaitForLoadStop(active_contents())); + EXPECT_EQ(active_contents()->GetAllFrames().size(), 2u) << + "Scripts from a.com should be temporarily allowed after reload."; + + // same doc navigation + ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("a.com", "/load_js_from_origins.html#foo")); + EXPECT_TRUE(WaitForLoadStop(active_contents())); + EXPECT_EQ(active_contents()->GetAllFrames().size(), 2u) << + "Scripts from a.com should be temporarily allowed for same doc navigation."; + + // navigate to a different origin + ui_test_utils::NavigateToURL( + browser(), + embedded_test_server()->GetURL("b.com", "/load_js_from_origins.html")); + EXPECT_TRUE(WaitForLoadStop(active_contents())); + EXPECT_EQ(active_contents()->GetAllFrames().size(), 1u) << + "All script loadings should be blocked after navigating away."; +} diff --git a/common/extensions/api/brave_shields.json b/common/extensions/api/brave_shields.json index e15f14e5da5f..bc50fb090199 100644 --- a/common/extensions/api/brave_shields.json +++ b/common/extensions/api/brave_shields.json @@ -29,13 +29,24 @@ ], "functions": [ { - "name": "dummy", + "name": "allowScriptsOnce", "type": "function", - "description": "Remove me when there were actual fucntions implemented", - "properties": [ + "description": "Allow scripts from a list of origins until next reload", + "parameters": [ { - "type": "integer", - "name": "dummyArg" + "name": "origins", + "type": "array", + "items": {"type": "string"} + }, + { + "name": "tabID", + "type": "integer" + }, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [] } ] } diff --git a/components/brave_shields/browser/BUILD.gn b/components/brave_shields/browser/BUILD.gn index 789daab6bb4e..876eb37dd8a5 100644 --- a/components/brave_shields/browser/BUILD.gn +++ b/components/brave_shields/browser/BUILD.gn @@ -25,6 +25,7 @@ source_set("brave_shields") { "//brave/vendor/tracking-protection/brave:tracking-protection", ] public_deps = [ + "//brave/content:common", "//chrome/common", "//third_party/leveldatabase", ] diff --git a/components/brave_shields/browser/brave_shields_web_contents_observer.cc b/components/brave_shields/browser/brave_shields_web_contents_observer.cc index fa873bfeb56e..099f4f15959f 100644 --- a/components/brave_shields/browser/brave_shields_web_contents_observer.cc +++ b/components/brave_shields/browser/brave_shields_web_contents_observer.cc @@ -10,6 +10,7 @@ #include "brave/common/render_messages.h" #include "brave/components/brave_shields/browser/brave_shields_util.h" #include "brave/components/brave_shields/common/brave_shield_constants.h" +#include "brave/content/common/frame_messages.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/profiles/profile.h" @@ -306,6 +307,22 @@ void BraveShieldsWebContentsObserver::ReadyToCommitNavigation( original_referrer.policy, &new_referrer)) { navigation_entry->SetReferrer(new_referrer); } + + // when the main frame navigate away + if (navigation_handle->IsInMainFrame() && + !navigation_handle->IsSameDocument() && + navigation_handle->GetReloadType() == content::ReloadType::NONE) { + allowed_script_origins_.clear(); + } + + navigation_handle->GetWebContents()->SendToAllFrames( + new BraveFrameMsg_AllowScriptsOnce( + MSG_ROUTING_NONE, allowed_script_origins_)); +} + +void BraveShieldsWebContentsObserver::AllowScriptsOnce( + const std::vector& origins, WebContents* contents) { + allowed_script_origins_ = std::move(origins); } } // namespace brave_shields diff --git a/components/brave_shields/browser/brave_shields_web_contents_observer.h b/components/brave_shields/browser/brave_shields_web_contents_observer.h index 70ac078a03c5..3dc64f9c393f 100644 --- a/components/brave_shields/browser/brave_shields_web_contents_observer.h +++ b/components/brave_shields/browser/brave_shields_web_contents_observer.h @@ -36,6 +36,8 @@ class BraveShieldsWebContentsObserver : public content::WebContentsObserver, int render_process_id, int render_frame_id, int frame_tree_node_id); static GURL GetTabURLFromRenderFrameInfo(int render_process_id, int render_frame_id); + void AllowScriptsOnce(const std::vector& origins, + content::WebContents* web_contents); protected: // A set of identifiers that uniquely identifies a RenderFrame. @@ -75,6 +77,11 @@ class BraveShieldsWebContentsObserver : public content::WebContentsObserver, // This lock protects |frame_data_map_| from being concurrently written on the // UI thread and read on the IO thread. static base::Lock frame_data_map_lock_; + + private: + friend class content::WebContentsUserData; + std::vector allowed_script_origins_; + DISALLOW_COPY_AND_ASSIGN(BraveShieldsWebContentsObserver); }; diff --git a/content/BUILD.gn b/content/BUILD.gn new file mode 100644 index 000000000000..ef170593eff6 --- /dev/null +++ b/content/BUILD.gn @@ -0,0 +1,10 @@ +source_set("common") { + sources = [ + "common/content_message_generator.cc", + "common/content_message_generator.h", + ] + + deps = [ + "//ipc" + ] +} diff --git a/content/common/content_message_generator.cc b/content/common/content_message_generator.cc new file mode 100644 index 000000000000..5056d1d74565 --- /dev/null +++ b/content/common/content_message_generator.cc @@ -0,0 +1,30 @@ +// Get basic type definitions. +#define IPC_MESSAGE_IMPL + +#include "brave/content/common/content_message_generator.h" + +// Generate constructors. +#include "ipc/struct_constructor_macros.h" +#include "brave/content/common/content_message_generator.h" + +// Generate destructors. +#include "ipc/struct_destructor_macros.h" +#include "brave/content/common/content_message_generator.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#include "brave/content/common/content_message_generator.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#include "brave/content/common/content_message_generator.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#include "brave/content/common/content_message_generator.h" +} // namespace IPC diff --git a/content/common/content_message_generator.h b/content/common/content_message_generator.h new file mode 100644 index 000000000000..425ca481b122 --- /dev/null +++ b/content/common/content_message_generator.h @@ -0,0 +1,2 @@ +// Multiply-included file, no traditional include guard. +#include "brave/content/common/frame_messages.h" diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h new file mode 100644 index 000000000000..f9d4b9d9f030 --- /dev/null +++ b/content/common/frame_messages.h @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Multiply-included file, no traditional include guard. +#include + +#include "ipc/ipc_message_macros.h" + +// The message starter should be declared in ipc/ipc_message_start.h. Since +// we don't want to patch Chromium, we just pretend to be Content Shell. + +#define IPC_MESSAGE_START ShellMsgStart + +// Tell RenderFrame(s) to temporary allow scripts from a list of origins once. +IPC_MESSAGE_ROUTED1(BraveFrameMsg_AllowScriptsOnce, + std::vector /* origins to allow scripts once */) diff --git a/renderer/BUILD.gn b/renderer/BUILD.gn index 291d02ebeeba..7d5dd4fe8262 100644 --- a/renderer/BUILD.gn +++ b/renderer/BUILD.gn @@ -16,5 +16,6 @@ source_set("renderer") { "//skia", "//third_party/WebKit/public:blink", "//brave/chromium_src:renderer", + "//brave/content:common", ] } diff --git a/renderer/brave_content_settings_observer.cc b/renderer/brave_content_settings_observer.cc index 9cb820b94a22..dba692b25d88 100644 --- a/renderer/brave_content_settings_observer.cc +++ b/renderer/brave_content_settings_observer.cc @@ -6,6 +6,7 @@ #include "base/strings/utf_string_conversions.h" #include "brave/common/render_messages.h" +#include "brave/content/common/frame_messages.h" #include "components/content_settings/core/common/content_settings_pattern.h" #include "content/public/renderer/render_frame.h" #include "services/service_manager/public/cpp/interface_provider.h" @@ -25,16 +26,62 @@ BraveContentSettingsObserver::BraveContentSettingsObserver( BraveContentSettingsObserver::~BraveContentSettingsObserver() { } +bool BraveContentSettingsObserver::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(BraveContentSettingsObserver, message) + IPC_MESSAGE_HANDLER(BraveFrameMsg_AllowScriptsOnce, OnAllowScriptsOnce) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + if (handled) return true; + return ContentSettingsObserver::OnMessageReceived(message); +} + +void BraveContentSettingsObserver::OnAllowScriptsOnce( + const std::vector& origins) { + preloaded_temporarily_allowed_scripts_ = std::move(origins); +} + +void BraveContentSettingsObserver::DidCommitProvisionalLoad( + bool is_new_navigation, bool is_same_document_navigation) { + if (!is_same_document_navigation) { + temporarily_allowed_scripts_ = + std::move(preloaded_temporarily_allowed_scripts_); + } + + ContentSettingsObserver::DidCommitProvisionalLoad( + is_new_navigation, is_same_document_navigation); +} + +bool BraveContentSettingsObserver::IsScriptTemporilyAllowed( + const GURL& script_url) { + // check if scripts from this origin are temporily allowed or not + return base::ContainsKey(temporarily_allowed_scripts_, + script_url.GetOrigin().spec()); +} + void BraveContentSettingsObserver::BraveSpecificDidBlockJavaScript( const base::string16& details) { Send(new BraveViewHostMsg_JavaScriptBlocked(routing_id(), details)); } +bool BraveContentSettingsObserver::AllowScript( + bool enabled_per_settings) { + bool allow = ContentSettingsObserver::AllowScript(enabled_per_settings); + blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); + allow = allow || IsScriptTemporilyAllowed( + url::Origin(frame->GetDocument().GetSecurityOrigin()).GetURL()); + + return allow; +} + bool BraveContentSettingsObserver::AllowScriptFromSource( bool enabled_per_settings, const blink::WebURL& script_url) { bool allow = ContentSettingsObserver::AllowScriptFromSource( enabled_per_settings, script_url); + allow = allow || IsScriptTemporilyAllowed(GURL(script_url)); + if (!allow) { const GURL secondary_url(script_url); BraveSpecificDidBlockJavaScript(base::UTF8ToUTF16(secondary_url.spec())); diff --git a/renderer/brave_content_settings_observer.h b/renderer/brave_content_settings_observer.h index 88a362c717ad..9242aecdf8a1 100644 --- a/renderer/brave_content_settings_observer.h +++ b/renderer/brave_content_settings_observer.h @@ -25,6 +25,7 @@ class BraveContentSettingsObserver ~BraveContentSettingsObserver() override; protected: + bool AllowScript(bool enabled_per_settings) override; bool AllowScriptFromSource(bool enabled_per_settings, const blink::WebURL& script_url) override; @@ -44,6 +45,21 @@ class BraveContentSettingsObserver const blink::WebLocalFrame* frame, const GURL& secondary_url); + // RenderFrameObserver + bool OnMessageReceived(const IPC::Message& message) override; + void OnAllowScriptsOnce(const std::vector& origins); + void DidCommitProvisionalLoad(bool is_new_navigation, + bool is_same_document_navigation) override; + + bool IsScriptTemporilyAllowed(const GURL& script_url); + + // Origins of scripts which are temporary allowed for this frame in the + // current load + base::flat_set temporarily_allowed_scripts_; + + // temporary allowed script origins we preloaded for the next load + base::flat_set preloaded_temporarily_allowed_scripts_; + DISALLOW_COPY_AND_ASSIGN(BraveContentSettingsObserver); }; diff --git a/test/BUILD.gn b/test/BUILD.gn index b95dbcdca408..5843d5315084 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -71,6 +71,7 @@ group("brave_browser_tests_deps") { test("brave_browser_tests") { sources = [ + "//brave/browser/extensions/api/brave_shields_api_browsertest.cc", "//brave/components/brave_shields/browser/ad_block_service_browsertest.cc", "//brave/components/brave_shields/browser/https_everywhere_service_browsertest.cc", "//brave/components/brave_shields/browser/tracking_protection_service_browsertest.cc", @@ -80,6 +81,8 @@ test("brave_browser_tests") { "//chrome/browser/extensions/browsertest_util.h", "//chrome/browser/extensions/extension_browsertest.cc", "//chrome/browser/extensions/extension_browsertest.h", + "//chrome/browser/extensions/extension_function_test_utils.cc", + "//chrome/browser/extensions/extension_function_test_utils.h", "//chrome/browser/extensions/updater/extension_cache_fake.cc", "//chrome/browser/extensions/updater/extension_cache_fake.h", ] diff --git a/test/data/create_iframe.js b/test/data/create_iframe.js new file mode 100644 index 000000000000..6d5fe4c792d8 --- /dev/null +++ b/test/data/create_iframe.js @@ -0,0 +1,2 @@ +var frame = document.createElement('iframe'); +document.body.appendChild(frame); diff --git a/test/data/load_js_from_origins.html b/test/data/load_js_from_origins.html new file mode 100644 index 000000000000..e50c08ce8af9 --- /dev/null +++ b/test/data/load_js_from_origins.html @@ -0,0 +1,11 @@ +load js from origins + + + + +