From 9c00d71b5eaebf64ff828553b032c0f05ed6cc8b Mon Sep 17 00:00:00 2001 From: David Roger Date: Wed, 6 Sep 2017 12:42:25 +0000 Subject: [PATCH] Prevent extensions from accessing the Dice HTTP response header Gaia can send a Oauth2 authorization code in the Dice response header. This is very sensitive information, and may allow an extension to generate a refresh token for the user account. For this reason, we choose to hide the Dice response headers to extensions. This header should be only hidden when sent from a Gaia origin, otherwise this could allow a website to hide information from extensions. This CL adds support for hiding response headers to extensions, and affects the web_request and declarative_web_request APIs. TBR=droger@chromium.org (cherry picked from commit 1f0a8bf74dac6a17b96a29e694f36121f6425a80) Bug: 757478 Change-Id: I79adc8ae7bfad828647f1a8bd792a2976a69e280 Reviewed-on: https://chromium-review.googlesource.com/629081 Reviewed-by: Devlin Reviewed-by: Mihai Sardarescu Commit-Queue: David Roger Cr-Original-Commit-Position: refs/heads/master@{#499173} Reviewed-on: https://chromium-review.googlesource.com/652449 Reviewed-by: David Roger Cr-Commit-Position: refs/branch-heads/3202@{#42} Cr-Branched-From: fa6a5d87adff761bc16afc5498c3f5944c1daa68-refs/heads/master@{#499098} --- chrome/browser/extensions/BUILD.gn | 1 + .../api/chrome_extensions_api_client.cc | 17 +++ .../api/chrome_extensions_api_client.h | 2 + .../chrome_extensions_api_client_unittest.cc | 25 ++++ .../web_request/web_request_api_unittest.cc | 42 ++++++- .../api/web_request/web_request_apitest.cc | 118 ++++++++++++++++++ .../web_request_event_details_unittest.cc | 66 ++++++++++ chrome/browser/signin/chrome_signin_helper.cc | 1 - chrome/test/BUILD.gn | 2 + .../webrequest_dice_header/background.js | 33 +++++ .../webrequest_dice_header/manifest.json | 8 ++ chrome/test/data/extensions/dice.html | 3 + .../extensions/dice.html.mock-http-headers | 5 + .../core/browser/signin_header_helper.cc | 5 +- .../core/browser/signin_header_helper.h | 1 + .../webrequest_condition_attribute.cc | 4 + ...webrequest_condition_attribute_unittest.cc | 68 ++++++++++ .../browser/api/extensions_api_client.cc | 6 + .../browser/api/extensions_api_client.h | 8 ++ .../api/web_request/web_request_api.cc | 9 +- .../web_request/web_request_api_helpers.cc | 10 +- .../api/web_request/web_request_api_helpers.h | 3 + .../web_request/web_request_event_details.cc | 9 +- 23 files changed, 430 insertions(+), 16 deletions(-) create mode 100644 chrome/browser/extensions/api/chrome_extensions_api_client_unittest.cc create mode 100644 chrome/test/data/extensions/api_test/webrequest_dice_header/background.js create mode 100644 chrome/test/data/extensions/api_test/webrequest_dice_header/manifest.json create mode 100644 chrome/test/data/extensions/dice.html create mode 100644 chrome/test/data/extensions/dice.html.mock-http-headers diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn index 3e3b1885ec4bc..5e6fcdbf3505b 100644 --- a/chrome/browser/extensions/BUILD.gn +++ b/chrome/browser/extensions/BUILD.gn @@ -872,6 +872,7 @@ static_library("extensions") { "//extensions/common/api", "//extensions/features", "//extensions/strings", + "//google_apis", "//media:media_features", "//net", "//ppapi/features", diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client.cc b/chrome/browser/extensions/api/chrome_extensions_api_client.cc index 48d7da4fe0cfa..58646f752a264 100644 --- a/chrome/browser/extensions/api/chrome_extensions_api_client.cc +++ b/chrome/browser/extensions/api/chrome_extensions_api_client.cc @@ -4,9 +4,12 @@ #include "chrome/browser/extensions/api/chrome_extensions_api_client.h" +#include + #include "base/bind.h" #include "base/files/file_path.h" #include "base/memory/ptr_util.h" +#include "base/strings/string_util.h" #include "build/build_config.h" #include "chrome/browser/data_use_measurement/data_use_web_contents_observer.h" #include "chrome/browser/extensions/api/chrome_device_permissions_prompt.h" @@ -31,12 +34,15 @@ #include "chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.h" #include "chrome/browser/ui/pdf/chrome_pdf_web_contents_helper_client.h" #include "components/pdf/browser/pdf_web_contents_helper.h" +#include "components/signin/core/browser/signin_header_helper.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_delegate.h" #include "extensions/browser/guest_view/web_view/web_view_guest.h" #include "extensions/browser/guest_view/web_view/web_view_permission_helper.h" +#include "google_apis/gaia/gaia_urls.h" #include "printing/features/features.h" +#include "url/gurl.h" #if defined(OS_CHROMEOS) #include "chrome/browser/extensions/api/file_handlers/non_native_file_system_delegate_chromeos.h" @@ -85,6 +91,17 @@ void ChromeExtensionsAPIClient::AttachWebContentsHelpers( web_contents); } +bool ChromeExtensionsAPIClient::ShouldHideResponseHeader( + const GURL& url, + const std::string& header_name) const { + // Gaia may send a OAUth2 authorization code in the Dice response header, + // which could allow an extension to generate a refresh token for the account. + return ( + (url.host_piece() == GaiaUrls::GetInstance()->gaia_url().host_piece()) && + (base::CompareCaseInsensitiveASCII(header_name, + signin::kDiceResponseHeader) == 0)); +} + AppViewGuestDelegate* ChromeExtensionsAPIClient::CreateAppViewGuestDelegate() const { return new ChromeAppViewGuestDelegate(); diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client.h b/chrome/browser/extensions/api/chrome_extensions_api_client.h index b1c93a7ce2b4d..eb300332f5b2b 100644 --- a/chrome/browser/extensions/api/chrome_extensions_api_client.h +++ b/chrome/browser/extensions/api/chrome_extensions_api_client.h @@ -30,6 +30,8 @@ class ChromeExtensionsAPIClient : public ExtensionsAPIClient { override; void AttachWebContentsHelpers(content::WebContents* web_contents) const override; + bool ShouldHideResponseHeader(const GURL& url, + const std::string& header_name) const override; AppViewGuestDelegate* CreateAppViewGuestDelegate() const override; ExtensionOptionsGuestDelegate* CreateExtensionOptionsGuestDelegate( ExtensionOptionsGuest* guest) const override; diff --git a/chrome/browser/extensions/api/chrome_extensions_api_client_unittest.cc b/chrome/browser/extensions/api/chrome_extensions_api_client_unittest.cc new file mode 100644 index 0000000000000..8e2738c02a5cb --- /dev/null +++ b/chrome/browser/extensions/api/chrome_extensions_api_client_unittest.cc @@ -0,0 +1,25 @@ +// Copyright 2017 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 "chrome/browser/extensions/api/chrome_extensions_api_client.h" + +#include "google_apis/gaia/gaia_urls.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace extensions { + +TEST(TestChromeExtensionsAPIClient, ShouldHideResponseHeader) { + ChromeExtensionsAPIClient client; + EXPECT_TRUE(client.ShouldHideResponseHeader( + GaiaUrls::GetInstance()->gaia_url(), "X-Chrome-ID-Consistency-Response")); + EXPECT_TRUE(client.ShouldHideResponseHeader( + GaiaUrls::GetInstance()->gaia_url(), "x-cHroMe-iD-CoNsiStenCY-RESPoNSE")); + EXPECT_FALSE(client.ShouldHideResponseHeader( + GURL("http://www.example.com"), "X-Chrome-ID-Consistency-Response")); + EXPECT_FALSE(client.ShouldHideResponseHeader( + GaiaUrls::GetInstance()->gaia_url(), "Google-Accounts-SignOut")); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc index 944e035c9cfa0..6e29c8b081235 100644 --- a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc +++ b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc @@ -50,12 +50,15 @@ #include "extensions/common/api/web_request.h" #include "extensions/common/extension_messages.h" #include "extensions/common/features/feature.h" +#include "google_apis/gaia/gaia_urls.h" #include "net/base/auth.h" #include "net/base/elements_upload_data_stream.h" #include "net/base/request_priority.h" #include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_file_element_reader.h" #include "net/dns/mock_host_resolver.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" #include "net/log/net_log_with_source.h" #include "net/log/test_net_log.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" @@ -1485,6 +1488,7 @@ TEST(ExtensionWebRequestHelpersTest, TestCalculateOnHeadersReceivedDelta) { "Key2: Value2, Bar\r\n" "Key3: Value3\r\n" "Key5: Value5, end5\r\n" + "X-Chrome-ID-Consistency-Response: Value6\r\n" "\r\n"; scoped_refptr base_headers( new net::HttpResponseHeaders( @@ -1497,11 +1501,39 @@ TEST(ExtensionWebRequestHelpersTest, TestCalculateOnHeadersReceivedDelta) { // Key3 is deleted new_headers.push_back(ResponseHeader("Key4", "Value4")); // Added new_headers.push_back(ResponseHeader("Key5", "Value5, end5")); // Unchanged - GURL effective_new_url; - - std::unique_ptr delta(CalculateOnHeadersReceivedDelta( - "extid", base::Time::Now(), cancel, effective_new_url, base_headers.get(), - &new_headers)); + new_headers.push_back(ResponseHeader("X-Chrome-ID-Consistency-Response", + "Value1")); // Modified + GURL url; + + // The X-Chrome-ID-Consistency-Response is a protected header, but only for + // Gaia URLs. It should be modifiable when sent from anywhere else. + // Non-Gaia URL: + std::unique_ptr delta( + CalculateOnHeadersReceivedDelta("extid", base::Time::Now(), cancel, url, + url, base_headers.get(), &new_headers)); + ASSERT_TRUE(delta.get()); + EXPECT_TRUE(delta->cancel); + EXPECT_EQ(3u, delta->added_response_headers.size()); + EXPECT_TRUE(base::ContainsValue(delta->added_response_headers, + ResponseHeader("Key2", "Value1"))); + EXPECT_TRUE(base::ContainsValue(delta->added_response_headers, + ResponseHeader("Key4", "Value4"))); + EXPECT_TRUE(base::ContainsValue( + delta->added_response_headers, + ResponseHeader("X-Chrome-ID-Consistency-Response", "Value1"))); + EXPECT_EQ(3u, delta->deleted_response_headers.size()); + EXPECT_TRUE(base::ContainsValue(delta->deleted_response_headers, + ResponseHeader("Key2", "Value2, Bar"))); + EXPECT_TRUE(base::ContainsValue(delta->deleted_response_headers, + ResponseHeader("Key3", "Value3"))); + EXPECT_TRUE(base::ContainsValue( + delta->deleted_response_headers, + ResponseHeader("X-Chrome-ID-Consistency-Response", "Value6"))); + + // Gaia URL: + delta.reset(CalculateOnHeadersReceivedDelta( + "extid", base::Time::Now(), cancel, GaiaUrls::GetInstance()->gaia_url(), + url, base_headers.get(), &new_headers)); ASSERT_TRUE(delta.get()); EXPECT_TRUE(delta->cancel); EXPECT_EQ(2u, delta->added_response_headers.size()); diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc index f5fcff5032073..873abeb4a9283 100644 --- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc +++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc @@ -30,6 +30,7 @@ #include "chromeos/login/scoped_test_public_session_login_state.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" +#include "content/public/browser/navigation_handle.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_frame_host.h" @@ -45,7 +46,9 @@ #include "extensions/common/features/feature.h" #include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" +#include "google_apis/gaia/gaia_switches.h" #include "net/dns/mock_host_resolver.h" +#include "net/http/http_util.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/test_data_directory.h" @@ -104,6 +107,10 @@ const char kPerformXhrJs[] = "};\n" "xhr.send();\n"; +// Header values set by the server and by the extension. +const char kHeaderValueFromExtension[] = "ValueFromExtension"; +const char kHeaderValueFromServer[] = "ValueFromServer"; + // Performs an XHR in the given |frame|, replying when complete. void PerformXhrInFrame(content::RenderFrameHost* frame, const std::string& host, @@ -204,6 +211,11 @@ class ExtensionWebRequestApiTest : public ExtensionApiTest { host_resolver()->AddRule("*", "127.0.0.1"); } + void SetUpCommandLine(base::CommandLine* command_line) override { + ExtensionApiTest::SetUpCommandLine(command_line); + command_line->AppendSwitchASCII(switches::kGaiaUrl, "http://gaia.com"); + } + void RunPermissionTest( const char* extension_directory, bool load_extension_with_incognito_permission, @@ -846,6 +858,112 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, GetWebRequestCountFromBackgroundPage(extension, profile())); } +// Checks that the Dice response header is protected for Gaia URLs, but not +// other URLs. +IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, + WebRequestDiceHeaderProtection) { + // Load an extension that registers a listener for webRequest events, and + // wait until it is initialized. + ExtensionTestMessageListener listener("ready", false); + const Extension* extension = + LoadExtension(test_data_dir_.AppendASCII("webrequest_dice_header")); + ASSERT_TRUE(extension) << message_; + EXPECT_TRUE(listener.WaitUntilSatisfied()); + + ASSERT_TRUE(embedded_test_server()->Start()); + + // Setup a web contents observer to inspect the response headers after the + // extension was run. + class TestWebContentsObserver : public content::WebContentsObserver { + public: + explicit TestWebContentsObserver(content::WebContents* contents) + : WebContentsObserver(contents) {} + + void DidFinishNavigation( + content::NavigationHandle* navigation_handle) override { + // Check that the extension cannot add a Dice header. + const net::HttpResponseHeaders* headers = + navigation_handle->GetResponseHeaders(); + EXPECT_TRUE(headers->GetNormalizedHeader( + "X-Chrome-ID-Consistency-Response", &dice_header_value_)); + EXPECT_TRUE( + headers->GetNormalizedHeader("X-New-Header", &new_header_value_)); + EXPECT_TRUE( + headers->GetNormalizedHeader("X-Control", &control_header_value_)); + did_finish_navigation_called_ = true; + } + + bool did_finish_navigation_called() const { + return did_finish_navigation_called_; + } + + const std::string& dice_header_value() const { return dice_header_value_; } + + const std::string& new_header_value() const { return new_header_value_; } + + const std::string& control_header_value() const { + return control_header_value_; + } + + void Clear() { + did_finish_navigation_called_ = false; + dice_header_value_.clear(); + new_header_value_.clear(); + control_header_value_.clear(); + } + + private: + bool did_finish_navigation_called_ = false; + std::string dice_header_value_; + std::string new_header_value_; + std::string control_header_value_; + }; + + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + TestWebContentsObserver test_webcontents_observer(web_contents); + + // Navigate to the Gaia URL intercepted by the extension. + GURL url = + embedded_test_server()->GetURL("gaia.com", "/extensions/dice.html"); + ui_test_utils::NavigateToURL(browser(), url); + + // Check that the Dice header was not changed by the extension. + EXPECT_TRUE(test_webcontents_observer.did_finish_navigation_called()); + EXPECT_EQ(kHeaderValueFromServer, + test_webcontents_observer.dice_header_value()); + EXPECT_EQ(kHeaderValueFromExtension, + test_webcontents_observer.new_header_value()); + EXPECT_EQ(kHeaderValueFromExtension, + test_webcontents_observer.control_header_value()); + + // Check that the Dice header cannot be read by the extension. + EXPECT_EQ(0, GetCountFromBackgroundPage(extension, profile(), + "window.diceResponseHeaderCount")); + EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), + "window.controlResponseHeaderCount")); + + // Navigate to a non-Gaia URL intercepted by the extension. + test_webcontents_observer.Clear(); + url = embedded_test_server()->GetURL("example.com", "/extensions/dice.html"); + ui_test_utils::NavigateToURL(browser(), url); + + // Check that the Dice header was changed by the extension. + EXPECT_TRUE(test_webcontents_observer.did_finish_navigation_called()); + EXPECT_EQ(kHeaderValueFromExtension, + test_webcontents_observer.dice_header_value()); + EXPECT_EQ(kHeaderValueFromExtension, + test_webcontents_observer.new_header_value()); + EXPECT_EQ(kHeaderValueFromExtension, + test_webcontents_observer.control_header_value()); + + // Check that the Dice header can be read by the extension. + EXPECT_EQ(1, GetCountFromBackgroundPage(extension, profile(), + "window.diceResponseHeaderCount")); + EXPECT_EQ(2, GetCountFromBackgroundPage(extension, profile(), + "window.controlResponseHeaderCount")); +} + // Test that the webRequest events are dispatched for the WebSocket handshake // requests. IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, WebSocketRequest) { diff --git a/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc b/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc index 4501c52484567..b68ccc592b8f9 100644 --- a/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc +++ b/chrome/browser/extensions/api/web_request/web_request_event_details_unittest.cc @@ -4,7 +4,17 @@ #include "extensions/browser/api/web_request/web_request_event_details.h" +#include "base/message_loop/message_loop.h" +#include "base/values.h" +#include "extensions/browser/api/web_request/web_request_api_constants.h" +#include "extensions/browser/api/web_request/web_request_api_helpers.h" +#include "google_apis/gaia/gaia_urls.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" +#include "net/traffic_annotation/network_traffic_annotation_test_helper.h" +#include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" namespace extensions { @@ -64,4 +74,60 @@ TEST(WebRequestEventDetailsTest, WhitelistedCopyForPublicSession) { EXPECT_EQ(arraysize(safe_attributes) + 1, copy->dict_.size()); } +TEST(WebRequestEventDetailsTest, SetResponseHeaders) { + const int kFilter = + extension_web_request_api_helpers::ExtraInfoSpec::RESPONSE_HEADERS; + base::MessageLoop message_loop; + net::TestURLRequestContext context; + + char headers_string[] = + "HTTP/1.0 200 OK\r\n" + "Key1: Value1\r\n" + "X-Chrome-ID-Consistency-Response: Value2\r\n" + "\r\n"; + scoped_refptr headers( + new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( + headers_string, sizeof(headers_string)))); + + { + // Non-Gaia URL. + std::unique_ptr request = context.CreateRequest( + GURL("http://www.example.com"), net::DEFAULT_PRIORITY, nullptr, + TRAFFIC_ANNOTATION_FOR_TESTS); + WebRequestEventDetails details(request.get(), kFilter); + details.SetResponseHeaders(request.get(), headers.get()); + std::unique_ptr dict = + details.GetFilteredDict(kFilter); + base::Value* filtered_headers = dict->FindPath({"responseHeaders"}); + ASSERT_TRUE(filtered_headers); + EXPECT_EQ(2u, filtered_headers->GetList().size()); + EXPECT_EQ("Key1", + filtered_headers->GetList()[0].FindPath({"name"})->GetString()); + EXPECT_EQ("Value1", + filtered_headers->GetList()[0].FindPath({"value"})->GetString()); + EXPECT_EQ("X-Chrome-ID-Consistency-Response", + filtered_headers->GetList()[1].FindPath({"name"})->GetString()); + EXPECT_EQ("Value2", + filtered_headers->GetList()[1].FindPath({"value"})->GetString()); + } + + { + // Gaia URL. + std::unique_ptr gaia_request = context.CreateRequest( + GaiaUrls::GetInstance()->gaia_url(), net::DEFAULT_PRIORITY, nullptr, + TRAFFIC_ANNOTATION_FOR_TESTS); + WebRequestEventDetails gaia_details(gaia_request.get(), kFilter); + gaia_details.SetResponseHeaders(gaia_request.get(), headers.get()); + std::unique_ptr dict = + gaia_details.GetFilteredDict(kFilter); + base::Value* filtered_headers = dict->FindPath({"responseHeaders"}); + ASSERT_TRUE(filtered_headers); + EXPECT_EQ(1u, filtered_headers->GetList().size()); + EXPECT_EQ("Key1", + filtered_headers->GetList()[0].FindPath({"name"})->GetString()); + EXPECT_EQ("Value1", + filtered_headers->GetList()[0].FindPath({"value"})->GetString()); + } +} + } // namespace extensions diff --git a/chrome/browser/signin/chrome_signin_helper.cc b/chrome/browser/signin/chrome_signin_helper.cc index dc66328eca04a..522d45e4a268d 100644 --- a/chrome/browser/signin/chrome_signin_helper.cc +++ b/chrome/browser/signin/chrome_signin_helper.cc @@ -42,7 +42,6 @@ namespace { const char kChromeManageAccountsHeader[] = "X-Chrome-Manage-Accounts"; #if BUILDFLAG(ENABLE_DICE_SUPPORT) -const char kDiceResponseHeader[] = "X-Chrome-ID-Consistency-Response"; const char kGoogleSignoutResponseHeader[] = "Google-Accounts-SignOut"; #endif diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index b5e154b5ebffb..b9b8add6f6feb 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -3970,6 +3970,7 @@ test("unit_tests") { "../browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc", "../browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api_unittest.cc", "../browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc", + "../browser/extensions/api/chrome_extensions_api_client_unittest.cc", "../browser/extensions/api/content_settings/content_settings_store_unittest.cc", "../browser/extensions/api/content_settings/content_settings_unittest.cc", "../browser/extensions/api/cookies/cookies_unittest.cc", @@ -4255,6 +4256,7 @@ test("unit_tests") { "//extensions/browser:test_support", "//extensions/common", "//extensions/strings", + "//google_apis", "//media/cast:test_support", # This will add all of the unit tests for the schema compiler to this diff --git a/chrome/test/data/extensions/api_test/webrequest_dice_header/background.js b/chrome/test/data/extensions/api_test/webrequest_dice_header/background.js new file mode 100644 index 0000000000000..336608315bc0b --- /dev/null +++ b/chrome/test/data/extensions/api_test/webrequest_dice_header/background.js @@ -0,0 +1,33 @@ +// Copyright 2017 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. + +window.diceResponseHeaderCount = 0; +window.controlResponseHeaderCount = 0; + +chrome.webRequest.onHeadersReceived.addListener(function(details) { + let diceHeaderFound = false; + const headerValue = 'ValueFromExtension' + const diceResponseHeader = 'X-Chrome-ID-Consistency-Response'; + details.responseHeaders.forEach(function(header) { + if (header.name == diceResponseHeader){ + ++window.diceResponseHeaderCount; + diceHeaderFound = true; + header.value = headerValue; + } else if (header.name == 'X-Control'){ + ++window.controlResponseHeaderCount; + header.value = headerValue; + } + }); + if (!diceHeaderFound) { + details.responseHeaders.push({name: diceResponseHeader, + value: headerValue}); + } + details.responseHeaders.push({name: 'X-New-Header', + value: headerValue}); + return {responseHeaders: details.responseHeaders}; +}, +{urls: ['http://*/extensions/dice.html']}, +['blocking', 'responseHeaders']); + +chrome.test.sendMessage('ready'); diff --git a/chrome/test/data/extensions/api_test/webrequest_dice_header/manifest.json b/chrome/test/data/extensions/api_test/webrequest_dice_header/manifest.json new file mode 100644 index 0000000000000..380aa9dac4068 --- /dev/null +++ b/chrome/test/data/extensions/api_test/webrequest_dice_header/manifest.json @@ -0,0 +1,8 @@ +{ + "name": "Reads and writes the Dice response header", + "description": "Reads and writes the Dice response header", + "manifest_version": 2, + "permissions": ["webRequest", "webRequestBlocking", ""], + "version": "0.1", + "background": {"scripts": ["background.js"]} +} diff --git a/chrome/test/data/extensions/dice.html b/chrome/test/data/extensions/dice.html new file mode 100644 index 0000000000000..3558a5f974304 --- /dev/null +++ b/chrome/test/data/extensions/dice.html @@ -0,0 +1,3 @@ + +dice test + diff --git a/chrome/test/data/extensions/dice.html.mock-http-headers b/chrome/test/data/extensions/dice.html.mock-http-headers new file mode 100644 index 0000000000000..23c7a46f164cd --- /dev/null +++ b/chrome/test/data/extensions/dice.html.mock-http-headers @@ -0,0 +1,5 @@ +HTTP/1.1 200 OK +Content-Type: text/html +Content-Length: -1 +X-Control: ValueFromServer +X-Chrome-ID-Consistency-Response: ValueFromServer diff --git a/components/signin/core/browser/signin_header_helper.cc b/components/signin/core/browser/signin_header_helper.cc index d1a77f041d557..ef836c4158f13 100644 --- a/components/signin/core/browser/signin_header_helper.cc +++ b/components/signin/core/browser/signin_header_helper.cc @@ -24,8 +24,9 @@ namespace signin { -extern const char kChromeConnectedHeader[] = "X-Chrome-Connected"; -extern const char kDiceRequestHeader[] = "X-Chrome-ID-Consistency-Request"; +const char kChromeConnectedHeader[] = "X-Chrome-Connected"; +const char kDiceRequestHeader[] = "X-Chrome-ID-Consistency-Request"; +const char kDiceResponseHeader[] = "X-Chrome-ID-Consistency-Response"; ManageAccountsParams::ManageAccountsParams() : service_type(GAIA_SERVICE_TYPE_NONE), diff --git a/components/signin/core/browser/signin_header_helper.h b/components/signin/core/browser/signin_header_helper.h index 0e7b617e93556..1e43ebb975fce 100644 --- a/components/signin/core/browser/signin_header_helper.h +++ b/components/signin/core/browser/signin_header_helper.h @@ -33,6 +33,7 @@ enum ProfileMode { extern const char kChromeConnectedHeader[]; extern const char kDiceRequestHeader[]; +extern const char kDiceResponseHeader[]; // The ServiceType specified by Gaia in the response header accompanying the 204 // response. This indicates the action Chrome is supposed to lead the user to diff --git a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc index 6fff29d23e59f..79c609a730c93 100644 --- a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc +++ b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute.cc @@ -22,6 +22,7 @@ #include "extensions/browser/api/declarative_webrequest/request_stage.h" #include "extensions/browser/api/declarative_webrequest/webrequest_condition.h" #include "extensions/browser/api/declarative_webrequest/webrequest_constants.h" +#include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/api/web_request/web_request_api_helpers.h" #include "extensions/browser/api/web_request/web_request_resource_type.h" #include "extensions/common/error_utils.h" @@ -691,6 +692,9 @@ bool WebRequestConditionAttributeResponseHeaders::IsFulfilled( std::string value; size_t iter = 0; while (!passed && headers->EnumerateHeaderLines(&iter, &name, &value)) { + if (ExtensionsAPIClient::Get()->ShouldHideResponseHeader( + request_data.request->url(), name)) + continue; passed |= header_matcher_->TestNameValue(name, value); } diff --git a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc index 77e9b1af0ddd4..b3c663ed7778d 100644 --- a/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc +++ b/extensions/browser/api/declarative_webrequest/webrequest_condition_attribute_unittest.cc @@ -19,12 +19,14 @@ #include "content/public/common/previews_state.h" #include "extensions/browser/api/declarative_webrequest/webrequest_condition.h" #include "extensions/browser/api/declarative_webrequest/webrequest_constants.h" +#include "extensions/browser/api/extensions_api_client.h" #include "net/base/request_priority.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" using base::DictionaryValue; using base::ListValue; @@ -531,6 +533,7 @@ TEST(WebRequestConditionAttributeTest, RequestHeaders) { // 2. Performing logical disjunction (||) between multiple specifications. // 3. Negating the match in case of 'doesNotContainHeaders'. TEST(WebRequestConditionAttributeTest, ResponseHeaders) { + ExtensionsAPIClient api_client; // Necessary for TestURLRequest. base::MessageLoopForIO message_loop; @@ -723,5 +726,70 @@ TEST(WebRequestConditionAttributeTest, ResponseHeaders) { EXPECT_FALSE(result); } +TEST(WebRequestConditionAttributeTest, HideResponseHeaders) { + // Necessary for TestURLRequest. + base::MessageLoopForIO message_loop; + + net::EmbeddedTestServer test_server; + test_server.ServeFilesFromDirectory(TestDataPath( + "chrome/test/data/extensions/api_test/webrequest/declarative")); + ASSERT_TRUE(test_server.Start()); + + net::TestURLRequestContext context; + net::TestDelegate delegate; + GURL url = test_server.GetURL("/headers.html"); + std::unique_ptr url_request(context.CreateRequest( + url, net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS)); + url_request->Start(); + base::RunLoop().Run(); + + // In all the test below we assume that the server includes the headers + // Custom-Header: custom/value + // Custom-Header-B: valueA + // Custom-Header-B: valueB + // Custom-Header-C: valueC, valueD + // Custom-Header-D: + + std::vector> tests; + bool result; + const RequestStage stage = ON_HEADERS_RECEIVED; + const std::string kCondition[] = {keys::kValueEqualsKey, "custom/value"}; + const size_t kConditionSizes[] = {arraysize(kCondition)}; + GetArrayAsVector(kCondition, kConditionSizes, 1u, &tests); + + { + // Default client does not hide the response header. + ExtensionsAPIClient api_client; + MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(), + &result); + EXPECT_TRUE(result); + } + + { + // Custom client hides the response header. + class TestExtensionsAPIClient : public ExtensionsAPIClient { + public: + TestExtensionsAPIClient(const GURL& url) : url_(url) {} + + private: + bool ShouldHideResponseHeader( + const GURL& url, + const std::string& header_name) const override { + // Check that the client is called with the right URL. + EXPECT_EQ(url_, url); + // Hide the header. + return header_name == "Custom-Header"; + } + + GURL url_; + }; + + TestExtensionsAPIClient api_client(url); + MatchAndCheck(tests, keys::kResponseHeadersKey, stage, url_request.get(), + &result); + EXPECT_FALSE(result); + } +} + } // namespace } // namespace extensions diff --git a/extensions/browser/api/extensions_api_client.cc b/extensions/browser/api/extensions_api_client.cc index 8cb900dcccfaa..3d8faf3d47ee7 100644 --- a/extensions/browser/api/extensions_api_client.cc +++ b/extensions/browser/api/extensions_api_client.cc @@ -38,6 +38,12 @@ void ExtensionsAPIClient::AttachWebContentsHelpers( content::WebContents* web_contents) const { } +bool ExtensionsAPIClient::ShouldHideResponseHeader( + const GURL& url, + const std::string& header_name) const { + return false; +} + AppViewGuestDelegate* ExtensionsAPIClient::CreateAppViewGuestDelegate() const { return NULL; } diff --git a/extensions/browser/api/extensions_api_client.h b/extensions/browser/api/extensions_api_client.h index 133a01441ce43..8a02d7874129d 100644 --- a/extensions/browser/api/extensions_api_client.h +++ b/extensions/browser/api/extensions_api_client.h @@ -7,6 +7,8 @@ #include #include +#include +#include #include "base/memory/ref_counted.h" #include "extensions/browser/api/clipboard/clipboard_api.h" @@ -14,6 +16,8 @@ #include "extensions/browser/api/storage/settings_namespace.h" #include "extensions/common/api/clipboard.h" +class GURL; + namespace base { template class ObserverListThreadSafe; @@ -85,6 +89,10 @@ class ExtensionsAPIClient { virtual void AttachWebContentsHelpers(content::WebContents* web_contents) const; + // Returns true if the header should be hidden to extensions. + virtual bool ShouldHideResponseHeader(const GURL& url, + const std::string& header_name) const; + // Creates the AppViewGuestDelegate. virtual AppViewGuestDelegate* CreateAppViewGuestDelegate() const; diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc index 5bf7837009de9..1b7b86dad6d39 100644 --- a/extensions/browser/api/web_request/web_request_api.cc +++ b/extensions/browser/api/web_request/web_request_api.cc @@ -1602,12 +1602,9 @@ helpers::EventResponseDelta* CalculateDelta( helpers::ResponseHeaders* new_headers = response->response_headers.get(); return helpers::CalculateOnHeadersReceivedDelta( - response->extension_id, - response->extension_install_time, - response->cancel, - response->new_url, - old_headers, - new_headers); + response->extension_id, response->extension_install_time, + response->cancel, blocked_request->request->url(), response->new_url, + old_headers, new_headers); } case ExtensionWebRequestEventRouter::kOnAuthRequired: return helpers::CalculateOnAuthRequiredDelta( diff --git a/extensions/browser/api/web_request/web_request_api_helpers.cc b/extensions/browser/api/web_request/web_request_api_helpers.cc index d29ff450ae704..1c5d9d46f523c 100644 --- a/extensions/browser/api/web_request/web_request_api_helpers.cc +++ b/extensions/browser/api/web_request/web_request_api_helpers.cc @@ -22,6 +22,7 @@ #include "components/web_cache/browser/web_cache_manager.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_process_host.h" +#include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/api/web_request/web_request_api_constants.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" @@ -329,6 +330,7 @@ EventResponseDelta* CalculateOnHeadersReceivedDelta( const std::string& extension_id, const base::Time& extension_install_time, bool cancel, + const GURL& old_url, const GURL& new_url, const net::HttpResponseHeaders* old_response_headers, ResponseHeaders* new_response_headers) { @@ -340,14 +342,18 @@ EventResponseDelta* CalculateOnHeadersReceivedDelta( if (!new_response_headers) return result; + extensions::ExtensionsAPIClient* api_client = + extensions::ExtensionsAPIClient::Get(); + // Find deleted headers (header keys are treated case insensitively). { size_t iter = 0; std::string name; std::string value; while (old_response_headers->EnumerateHeaderLines(&iter, &name, &value)) { + if (api_client->ShouldHideResponseHeader(old_url, name)) + continue; std::string name_lowercase = base::ToLowerASCII(name); - bool header_found = false; for (const auto& i : *new_response_headers) { if (base::LowerCaseEqualsASCII(i.first, name_lowercase) && @@ -364,6 +370,8 @@ EventResponseDelta* CalculateOnHeadersReceivedDelta( // Find added headers (header keys are treated case insensitively). { for (const auto& i : *new_response_headers) { + if (api_client->ShouldHideResponseHeader(old_url, i.first)) + continue; std::string name_lowercase = base::ToLowerASCII(i.first); size_t iter = 0; std::string name; diff --git a/extensions/browser/api/web_request/web_request_api_helpers.h b/extensions/browser/api/web_request/web_request_api_helpers.h index 5542c88b1d9c8..1faac8eeaa48d 100644 --- a/extensions/browser/api/web_request/web_request_api_helpers.h +++ b/extensions/browser/api/web_request/web_request_api_helpers.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "base/macros.h" #include "base/memory/linked_ptr.h" @@ -232,6 +234,7 @@ EventResponseDelta* CalculateOnHeadersReceivedDelta( const std::string& extension_id, const base::Time& extension_install_time, bool cancel, + const GURL& old_url, const GURL& new_url, const net::HttpResponseHeaders* old_response_headers, ResponseHeaders* new_response_headers); diff --git a/extensions/browser/api/web_request/web_request_event_details.cc b/extensions/browser/api/web_request/web_request_event_details.cc index f0b07bf8ee99e..c89edeb365cbe 100644 --- a/extensions/browser/api/web_request/web_request_event_details.cc +++ b/extensions/browser/api/web_request/web_request_event_details.cc @@ -5,6 +5,7 @@ #include "extensions/browser/api/web_request/web_request_event_details.h" #include +#include #include "base/callback.h" #include "base/memory/ptr_util.h" @@ -14,6 +15,7 @@ #include "content/public/browser/resource_request_info.h" #include "content/public/browser/websocket_handshake_request_info.h" #include "content/public/common/child_process_host.h" +#include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/api/web_request/upload_data_presenter.h" #include "extensions/browser/api/web_request/web_request_api_constants.h" #include "extensions/browser/api/web_request/web_request_api_helpers.h" @@ -154,8 +156,13 @@ void WebRequestEventDetails::SetResponseHeaders( size_t iter = 0; std::string name; std::string value; - while (response_headers->EnumerateHeaderLines(&iter, &name, &value)) + while (response_headers->EnumerateHeaderLines(&iter, &name, &value)) { + if (ExtensionsAPIClient::Get()->ShouldHideResponseHeader(request->url(), + name)) { + continue; + } headers->Append(helpers::CreateHeaderDictionary(name, value)); + } } response_headers_.reset(headers); }