Enforce policy host restrictions in regard to the same-origin policy.
Bug: 774731
Cq-Include-Trybots: luci.chromium.try:linux_mojo
Change-Id: I811af3256766f6cd7b4466b6b894067e74f652fd
Reviewed-on: https://chromium-review.googlesource.com/c/1050806
Commit-Queue: Nick Peterson <nrpeter@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Reviewed-by: Matt Menke <mmenke@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599859}
diff --git a/chrome/browser/extensions/background_xhr_browsertest.cc b/chrome/browser/extensions/background_xhr_browsertest.cc
index 4d4ab6d..e24cede 100644
--- a/chrome/browser/extensions/background_xhr_browsertest.cc
+++ b/chrome/browser/extensions/background_xhr_browsertest.cc
@@ -5,11 +5,13 @@
#include <memory>
#include "base/bind.h"
+#include "base/json/json_reader.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/ui/browser.h"
@@ -18,6 +20,7 @@
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
+#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_urls.h"
@@ -110,7 +113,7 @@
"test_http_auth.html", embedded_test_server()->GetURL("/auth-basic")));
}
-class BackgroundXhrWebstoreTest : public ExtensionApiTest {
+class BackgroundXhrWebstoreTest : public ExtensionApiTestWithManagementPolicy {
public:
BackgroundXhrWebstoreTest() = default;
~BackgroundXhrWebstoreTest() override = default;
@@ -131,59 +134,264 @@
ASSERT_TRUE(embedded_test_server()->Start());
}
+ bool CanFetch(const Extension* extension, const GURL& url) {
+ content::DOMMessageQueue message_queue;
+ browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+ profile(), extension->id(),
+ base::StringPrintf("canFetch('%s');", url.spec().c_str()));
+ std::string json;
+ EXPECT_TRUE(message_queue.WaitForMessage(&json));
+ base::JSONReader reader(base::JSON_ALLOW_TRAILING_COMMAS);
+ std::unique_ptr<base::Value> value = reader.ReadToValue(json);
+ std::string result;
+ EXPECT_TRUE(value->GetAsString(&result));
+ EXPECT_TRUE(result == "true" || result == "false") << result;
+ return result == "true";
+ }
+
+ const Extension* LoadXhrExtension(const std::string& host) {
+ ExtensionTestMessageListener listener("ready", false);
+ TestExtensionDir test_dir;
+ test_dir.WriteManifest(R"(
+ {
+ "name": "XHR Test",
+ "manifest_version": 2,
+ "version": "0.1",
+ "background": {"scripts": ["background.js"]},
+ "permissions": [")" + host + R"("]
+ })");
+ constexpr char kBackgroundScriptFile[] = R"(
+ function canFetch(url) {
+ console.warn('Fetching: ' + url);
+ fetch(url).then((response) => {
+ domAutomationController.send('true');
+ }).catch((e) => {
+ let message;
+ if (e.message == 'Failed to fetch')
+ message = 'false'
+ else
+ message = 'Unexpected Error: ' + e.message;
+ domAutomationController.send(message);
+ });
+ }
+ chrome.test.sendMessage('ready');)";
+
+ test_dir.WriteFile(FILE_PATH_LITERAL("background.js"),
+ kBackgroundScriptFile);
+ const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ return extension;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(BackgroundXhrWebstoreTest);
};
// Extensions should not be able to XHR to the webstore.
IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, XHRToWebstore) {
- TestExtensionDir test_dir;
- test_dir.WriteManifest(R"(
- {
- "name": "XHR Test",
- "manifest_version": 2,
- "version": "0.1",
- "background": {"scripts": ["background.js"]},
- "permissions": ["<all_urls>"]
- })");
- constexpr char kBackgroundScriptFile[] =
- R"(function canFetch(url) {
- console.warn('Fetching: ' + url);
- fetch(url).then((response) => {
- domAutomationController.send('true');
- }).catch((e) => {
- domAutomationController.send('false');
- });
- }
- chrome.test.sendMessage('ready');)";
-
- test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundScriptFile);
-
- ExtensionTestMessageListener listener("ready", false);
- const Extension* extension = LoadExtension(test_dir.UnpackedPath());
- ASSERT_TRUE(extension);
- EXPECT_TRUE(listener.WaitUntilSatisfied());
-
- content::BrowserContext* browser_context = profile();
- auto can_fetch = [extension, browser_context](const GURL& url) {
- std::string result = browsertest_util::ExecuteScriptInBackgroundPage(
- browser_context, extension->id(),
- base::StringPrintf("canFetch('%s');", url.spec().c_str()));
- EXPECT_TRUE(result == "true" || result == "false")
- << "Unexpected result: " << result;
- return result == "true";
- };
+ const Extension* extension = LoadXhrExtension("<all_urls>");
GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
GURL webstore_url_to_fetch = embedded_test_server()->GetURL(
webstore_launch_url.host(), "/simple.html");
- EXPECT_FALSE(can_fetch(webstore_url_to_fetch));
+ EXPECT_FALSE(CanFetch(extension, webstore_url_to_fetch));
// Sanity check: the extension should be able to fetch google.com.
GURL google_url =
embedded_test_server()->GetURL("google.com", "/simple.html");
- EXPECT_TRUE(can_fetch(google_url));
+ EXPECT_TRUE(CanFetch(extension, google_url));
+}
+
+// Extensions should not be able to XHR to the webstore regardless of policy.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, XHRToWebstorePolicy) {
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddPolicyAllowedHost(
+ "*", "*://" + extension_urls::GetWebstoreLaunchURL().host());
+ }
+
+ const Extension* extension = LoadXhrExtension("<all_urls>");
+
+ GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
+ GURL webstore_url_to_fetch = embedded_test_server()->GetURL(
+ webstore_launch_url.host(), "/simple.html");
+
+ EXPECT_FALSE(CanFetch(extension, webstore_url_to_fetch));
+
+ // Sanity check: the extension should be able to fetch google.com.
+ GURL google_url =
+ embedded_test_server()->GetURL("google.com", "/simple.html");
+ EXPECT_TRUE(CanFetch(extension, google_url));
+}
+
+// Extensions should not be able to bypass same-origin despite declaring
+// <all_urls> for hosts restricted by enterprise policy.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyBlockedXHR) {
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddPolicyBlockedHost("*", "*://*.example.com");
+ pref.AddPolicyAllowedHost("*", "*://public.example.com");
+ }
+
+ const Extension* extension = LoadXhrExtension("<all_urls>");
+
+ // Should block due to "runtime_blocked_hosts" section of policy.
+ GURL protected_url_to_fetch =
+ embedded_test_server()->GetURL("example.com", "/simple.html");
+ EXPECT_FALSE(CanFetch(extension, protected_url_to_fetch));
+
+ // Should allow due to "runtime_allowed_hosts" section of policy.
+ GURL exempted_url_to_fetch =
+ embedded_test_server()->GetURL("public.example.com", "/simple.html");
+ EXPECT_TRUE(CanFetch(extension, exempted_url_to_fetch));
+}
+
+// Verify that policy blocklists apply to XHRs done from injected scripts.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyContentScriptXHR) {
+ TestExtensionDir test_dir;
+ test_dir.WriteManifest(R"(
+ {
+ "name": "XHR Content Script Test",
+ "manifest_version": 2,
+ "version": "0.1",
+ "permissions": ["<all_urls>", "tabs"],
+ "background": {"scripts": ["background.js"]}
+ })");
+
+ constexpr char kBackgroundScript[] =
+ R"(function canFetch(url) {
+ chrome.tabs.executeScript({code: `
+ fetch("${url}")
+ .then(response => response.text())
+ .then(text => domAutomationController.send('true'))
+ .catch(err => domAutomationController.send('false'));
+ `});
+ }
+ )";
+ test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundScript);
+
+ const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+ ASSERT_TRUE(extension);
+
+ // Navigate to a foo.com page.
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ GURL page_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+ ui_test_utils::NavigateToURL(browser(), page_url);
+ EXPECT_EQ(page_url, web_contents->GetMainFrame()->GetLastCommittedURL());
+
+ GURL example_url =
+ embedded_test_server()->GetURL("example.com", "/simple.html");
+ GURL public_example_url =
+ embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+ // Sanity Check: Should be able to fetch cross origin.
+ EXPECT_TRUE(CanFetch(extension, example_url));
+ EXPECT_TRUE(CanFetch(extension, public_example_url));
+
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddPolicyBlockedHost("*", "*://*.example.com");
+ pref.AddPolicyAllowedHost("*", "*://public.example.com");
+ }
+
+ // Policies apply to XHR from a content script.
+ EXPECT_FALSE(CanFetch(extension, example_url));
+ EXPECT_TRUE(CanFetch(extension, public_example_url));
+}
+
+// Make sure the blocklist and allowlist update for both Default and Individual
+// scope policies. Testing with all host permissions granted (<all_urls>).
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyUpdateXHR) {
+ const Extension* extension = LoadXhrExtension("<all_urls>");
+
+ GURL example_url =
+ embedded_test_server()->GetURL("example.com", "/simple.html");
+ GURL public_example_url =
+ embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+ // Sanity check: Without restrictions all fetches should work.
+ EXPECT_TRUE(CanFetch(extension, public_example_url));
+ EXPECT_TRUE(CanFetch(extension, example_url));
+
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddPolicyBlockedHost("*", "*://*.example.com");
+ pref.AddPolicyAllowedHost("*", "*://public.example.com");
+ }
+
+ // Default policies propagate.
+ EXPECT_TRUE(CanFetch(extension, public_example_url));
+ EXPECT_FALSE(CanFetch(extension, example_url));
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddPolicyBlockedHost(extension->id(), "*://*.example2.com");
+ pref.AddPolicyAllowedHost(extension->id(), "*://public.example2.com");
+ }
+
+ // Default policies overridden when individual scope policies applied.
+ EXPECT_TRUE(CanFetch(extension, public_example_url));
+ EXPECT_TRUE(CanFetch(extension, example_url));
+
+ GURL example2_url =
+ embedded_test_server()->GetURL("example2.com", "/simple.html");
+ GURL public_example2_url =
+ embedded_test_server()->GetURL("public.example2.com", "/simple.html");
+
+ // Individual scope policies propagate.
+ EXPECT_TRUE(CanFetch(extension, public_example2_url));
+ EXPECT_FALSE(CanFetch(extension, example2_url));
+}
+
+// Make sure the allowlist entries added due to host permissions are removed
+// when a more generic blocklist policy is updated and contains them.
+// This tests the default policy scope update.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyUpdateDefaultXHR) {
+ const Extension* extension = LoadXhrExtension("*://public.example.com/*");
+
+ GURL example_url =
+ embedded_test_server()->GetURL("example.com", "/simple.html");
+ GURL public_example_url =
+ embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+ // Sanity check: Without restrictions only public.example.com should work.
+ EXPECT_TRUE(CanFetch(extension, public_example_url));
+ EXPECT_FALSE(CanFetch(extension, example_url));
+
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddPolicyBlockedHost("*", "*://*.example.com");
+ }
+
+ // The blocklist of example.com overrides allowlist of public.example.com.
+ EXPECT_FALSE(CanFetch(extension, example_url));
+ EXPECT_FALSE(CanFetch(extension, public_example_url));
+}
+
+// Make sure the allowlist entries added due to host permissions are removed
+// when a more generic blocklist policy is updated and contains them.
+// This tests an individual policy scope update.
+IN_PROC_BROWSER_TEST_F(BackgroundXhrWebstoreTest, PolicyUpdateIndividualXHR) {
+ const Extension* extension = LoadXhrExtension("*://public.example.com/*");
+
+ GURL example_url =
+ embedded_test_server()->GetURL("example.com", "/simple.html");
+ GURL public_example_url =
+ embedded_test_server()->GetURL("public.example.com", "/simple.html");
+
+ // Sanity check: Without restrictions only public.example.com should work.
+ EXPECT_TRUE(CanFetch(extension, public_example_url));
+ EXPECT_FALSE(CanFetch(extension, example_url));
+
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddPolicyBlockedHost(extension->id(), "*://*.example.com");
+ }
+
+ // The blocklist of example.com overrides allowlist of public.example.com.
+ EXPECT_FALSE(CanFetch(extension, example_url));
+ EXPECT_FALSE(CanFetch(extension, public_example_url));
}
} // namespace extensions
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index 697c21f..c05db45 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -404,7 +404,7 @@
ASSERT_TRUE(RunExtensionTest("content_scripts/policy")) << message_;
}
-// Verifies wildcard can be used for effecitve TLD.
+// Verifies wildcard can NOT be used for effective TLD.
IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
ContentScriptPolicyWildcard) {
// Set enterprise policy to block injection to policy specified hosts.
@@ -413,7 +413,7 @@
pref.AddPolicyBlockedHost("*", "*://example.*");
}
ASSERT_TRUE(StartEmbeddedTestServer());
- ASSERT_TRUE(RunExtensionTest("content_scripts/policy")) << message_;
+ ASSERT_FALSE(RunExtensionTest("content_scripts/policy")) << message_;
}
IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
diff --git a/chrome/browser/extensions/extension_management_internal.cc b/chrome/browser/extensions/extension_management_internal.cc
index 7b831fc6..18a8069a4 100644
--- a/chrome/browser/extensions/extension_management_internal.cc
+++ b/chrome/browser/extensions/extension_management_internal.cc
@@ -135,8 +135,10 @@
URLPattern pattern(extension_scheme_mask);
if (unparsed_str != URLPattern::kAllUrlsPattern)
unparsed_str.append("/*");
+ // TODO(nrpeter): Remove effective TLD wildcard capability from
+ // URLPattern.
URLPattern::ParseResult parse_result = pattern.Parse(
- unparsed_str, URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
+ unparsed_str, URLPattern::DENY_WILDCARD_FOR_EFFECTIVE_TLD);
if (parse_result != URLPattern::ParseResult::kSuccess) {
LOG(WARNING) << kMalformedPreferenceWarning;
LOG(WARNING) << "Invalid URL pattern '" + unparsed_str +
diff --git a/chrome/browser/extensions/extension_management_test_util.cc b/chrome/browser/extensions/extension_management_test_util.cc
index 21b667b..3eaab625 100644
--- a/chrome/browser/extensions/extension_management_test_util.cc
+++ b/chrome/browser/extensions/extension_management_test_util.cc
@@ -198,6 +198,34 @@
RemoveStringFromList(make_path(prefix, schema::kPolicyBlockedHosts), host);
}
+// Helper functions for 'runtime_allowed_hosts' manipulation ------------------
+
+void ExtensionManagementPrefUpdaterBase::UnsetPolicyAllowedHosts(
+ const std::string& prefix) {
+ DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+ pref_->Remove(make_path(prefix, schema::kPolicyAllowedHosts), nullptr);
+}
+
+void ExtensionManagementPrefUpdaterBase::ClearPolicyAllowedHosts(
+ const std::string& prefix) {
+ DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+ ClearList(make_path(prefix, schema::kPolicyAllowedHosts));
+}
+
+void ExtensionManagementPrefUpdaterBase::AddPolicyAllowedHost(
+ const std::string& prefix,
+ const std::string& host) {
+ DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+ AddStringToList(make_path(prefix, schema::kPolicyAllowedHosts), host);
+}
+
+void ExtensionManagementPrefUpdaterBase::RemovePolicyAllowedHost(
+ const std::string& prefix,
+ const std::string& host) {
+ DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix));
+ RemoveStringFromList(make_path(prefix, schema::kPolicyAllowedHosts), host);
+}
+
// Helper functions for 'allowed_permissions' manipulation ---------------------
void ExtensionManagementPrefUpdaterBase::UnsetAllowedPermissions(
diff --git a/chrome/browser/extensions/extension_management_test_util.h b/chrome/browser/extensions/extension_management_test_util.h
index dd05cc1..cda5537d 100644
--- a/chrome/browser/extensions/extension_management_test_util.h
+++ b/chrome/browser/extensions/extension_management_test_util.h
@@ -74,6 +74,14 @@
void RemovePolicyBlockedHost(const std::string& prefix,
const std::string& host);
+ // Helper functions for 'runtime_allowed_hosts' manipulation. |prefix| can be
+ // kWildCard or a valid extension ID.
+ void UnsetPolicyAllowedHosts(const std::string& prefix);
+ void ClearPolicyAllowedHosts(const std::string& prefix);
+ void AddPolicyAllowedHost(const std::string& prefix, const std::string& host);
+ void RemovePolicyAllowedHost(const std::string& prefix,
+ const std::string& host);
+
// Helper functions for 'allowed_permissions' manipulation. |id| must be a
// valid extension ID.
void UnsetAllowedPermissions(const std::string& id);
diff --git a/chrome/browser/extensions/policy_handlers.cc b/chrome/browser/extensions/policy_handlers.cc
index fc63cc0..4be1677 100644
--- a/chrome/browser/extensions/policy_handlers.cc
+++ b/chrome/browser/extensions/policy_handlers.cc
@@ -305,7 +305,7 @@
unparsed_urls->GetString(i, &unparsed_url);
URLPattern pattern(extension_scheme_mask);
URLPattern::ParseResult parse_result = pattern.Parse(
- unparsed_url, URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
+ unparsed_url, URLPattern::DENY_WILDCARD_FOR_EFFECTIVE_TLD);
// These keys don't support paths due to how we track the initiator
// of a webRequest and cookie security policy. We expect a valid
// pattern to return a PARSE_ERROR_EMPTY_PATH.
@@ -313,7 +313,7 @@
// Add a wildcard path to the URL as it should match any path.
parse_result =
pattern.Parse(unparsed_url + "/*",
- URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
+ URLPattern::DENY_WILDCARD_FOR_EFFECTIVE_TLD);
} else if (parse_result == URLPattern::ParseResult::kSuccess) {
// The user supplied a path, notify them that this is not supported.
if (!pattern.match_all_urls()) {
diff --git a/chrome/browser/extensions/policy_handlers_unittest.cc b/chrome/browser/extensions/policy_handlers_unittest.cc
index 01606b5..edbe164 100644
--- a/chrome/browser/extensions/policy_handlers_unittest.cc
+++ b/chrome/browser/extensions/policy_handlers_unittest.cc
@@ -96,15 +96,14 @@
}
TEST(ExtensionSettingsPolicyHandlerTest, CheckPolicySettingsURL) {
- std::vector<std::string> good_urls = {
- "*://*.example.com", "*://example.com", "http://cat.example.com",
- "https://example.*", "*://*.example.*", "<all_urls>"};
+ std::vector<std::string> good_urls = {"*://*.example.com", "*://example.com",
+ "http://cat.example.com", "<all_urls>"};
// Invalid URLPattern or with a non-standard path
std::vector<std::string> bad_urls = {
"://*.example.com", "*://example.com/cat*", "*://example.com/",
"*://*.example.com/*cat", "*://example.com/cat/*", "bad",
- "*://example.com/*"};
+ "*://example.com/*", "https://example.*", "*://*.example.*"};
// Crafts and parses a ExtensionSettings policy to test URL parsing.
auto url_parses_successfully = [](const char* policy_template,
diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
index 8306902..e31504f 100644
--- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
+++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc
@@ -74,12 +74,16 @@
// to avoid granting them in "unblessed" (non-extension) processes. If a
// component extension somehow starts as inactive and becomes active later,
// we'll re-init the origin permissions, so there's no danger in being
- // conservative.
+ // conservative. Components shouldn't be subject to enterprise policy controls
+ // or blocking access to the webstore so they get the highest priority
+ // allowlist entry.
if (extensions::Manifest::IsComponentLocation(extension.location()) &&
is_extension_active) {
blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(
extension.url(), blink::WebString::FromUTF8(content::kChromeUIScheme),
- blink::WebString::FromUTF8(chrome::kChromeUIThemeHost), false);
+ blink::WebString::FromUTF8(chrome::kChromeUIThemeHost),
+ false /*allow_destination_subdomains*/,
+ network::mojom::CORSOriginAccessMatchPriority::kMaxPriority);
}
// TODO(jstritar): We should try to remove this special case. Also, these
@@ -89,7 +93,9 @@
extensions::APIPermission::kManagement)) {
blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(
extension.url(), blink::WebString::FromUTF8(content::kChromeUIScheme),
- blink::WebString::FromUTF8(chrome::kChromeUIExtensionIconHost), false);
+ blink::WebString::FromUTF8(chrome::kChromeUIExtensionIconHost),
+ false /*allow_destination_subdomains*/,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
}
}
diff --git a/content/browser/loader/cors_origin_access_list_browsertest.cc b/content/browser/loader/cors_origin_access_list_browsertest.cc
index b832ca4..8f32876 100644
--- a/content/browser/loader/cors_origin_access_list_browsertest.cc
+++ b/content/browser/loader/cors_origin_access_list_browsertest.cc
@@ -21,6 +21,7 @@
#include "net/base/host_port_pair.h"
#include "net/dns/mock_host_resolver.h"
#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/cors.mojom.h"
#include "services/network/public/mojom/cors_origin_pattern.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
@@ -102,8 +103,9 @@
const std::string& host,
bool allow_subdomains) {
std::vector<network::mojom::CorsOriginPatternPtr> list1;
- list1.push_back(
- network::mojom::CorsOriginPattern::New(scheme, host, allow_subdomains));
+ list1.push_back(network::mojom::CorsOriginPattern::New(
+ scheme, host, allow_subdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
bool first_list_done = false;
BrowserContext::SetCorsOriginAccessListsForOrigin(
shell()->web_contents()->GetBrowserContext(),
@@ -113,8 +115,9 @@
base::Unretained(&first_list_done)));
std::vector<network::mojom::CorsOriginPatternPtr> list2;
- list2.push_back(
- network::mojom::CorsOriginPattern::New(scheme, host, allow_subdomains));
+ list2.push_back(network::mojom::CorsOriginPattern::New(
+ scheme, host, allow_subdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
bool second_list_done = false;
BrowserContext::SetCorsOriginAccessListsForOrigin(
shell()->web_contents()->GetBrowserContext(),
diff --git a/content/public/test/network_service_test_helper.cc b/content/public/test/network_service_test_helper.cc
index 2d81fbb5a..6556ecc 100644
--- a/content/public/test/network_service_test_helper.cc
+++ b/content/public/test/network_service_test_helper.cc
@@ -41,9 +41,11 @@
namespace content {
namespace {
+#ifndef STATIC_ASSERT_ENUM
#define STATIC_ASSERT_ENUM(a, b) \
static_assert(static_cast<int>(a) == static_cast<int>(b), \
"mismatching enums: " #a)
+#endif
STATIC_ASSERT_ENUM(network::mojom::ResolverType::kResolverTypeFail,
net::RuleBasedHostResolverProc::Rule::kResolverTypeFail);
diff --git a/content/shell/test_runner/DEPS b/content/shell/test_runner/DEPS
index 8796790..42b1e66 100644
--- a/content/shell/test_runner/DEPS
+++ b/content/shell/test_runner/DEPS
@@ -11,6 +11,7 @@
"+net/base",
"+services/device/public/cpp/generic_sensor",
"+services/device/public/mojom",
+ "+services/network/public/mojom/cors.mojom.h",
"+services/service_manager/public/cpp",
"+skia",
"+third_party/khronos/GLES2/gl2.h",
diff --git a/content/shell/test_runner/test_runner.cc b/content/shell/test_runner/test_runner.cc
index 653d3b1b..dcabe31 100644
--- a/content/shell/test_runner/test_runner.cc
+++ b/content/shell/test_runner/test_runner.cc
@@ -38,6 +38,7 @@
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
+#include "services/network/public/mojom/cors.mojom.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_registration.h"
#include "third_party/blink/public/platform/web_data.h"
#include "third_party/blink/public/platform/web_point.h"
@@ -2109,7 +2110,8 @@
blink::WebSecurityPolicy::AddOriginAccessAllowListEntry(
url, blink::WebString::FromUTF8(destination_protocol),
blink::WebString::FromUTF8(destination_host),
- allow_destination_subdomains);
+ allow_destination_subdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
}
void TestRunner::SetTextSubpixelPositioning(bool value) {
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index da2c272..25959d38 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -100,6 +100,7 @@
#include "extensions/renderer/worker_thread_dispatcher.h"
#include "gin/converter.h"
#include "mojo/public/js/grit/mojo_bindings_resources.h"
+#include "services/network/public/mojom/cors.mojom.h"
#include "third_party/blink/public/platform/web_runtime_features.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_request.h"
@@ -1104,6 +1105,16 @@
const ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params& params) {
PermissionsData::SetDefaultPolicyHostRestrictions(
params.default_policy_blocked_hosts, params.default_policy_allowed_hosts);
+ // Update blink host permission allowlist exceptions for all loaded
+ // extensions.
+ for (const std::string& extension_id :
+ RendererExtensionRegistry::Get()->GetIDs()) {
+ const Extension* extension =
+ RendererExtensionRegistry::Get()->GetByID(extension_id);
+ if (extension->permissions_data()->UsesDefaultPolicyHostRestrictions()) {
+ UpdateOriginPermissions(*extension);
+ }
+ }
UpdateBindings(std::string());
}
@@ -1114,6 +1125,13 @@
if (!extension)
return;
+ if (params.uses_default_policy_host_restrictions) {
+ extension->permissions_data()->SetUsesDefaultHostRestrictions();
+ } else {
+ extension->permissions_data()->SetPolicyHostRestrictions(
+ params.policy_blocked_hosts, params.policy_allowed_hosts);
+ }
+
std::unique_ptr<const PermissionSet> active =
params.active_permissions.ToPermissionSet();
std::unique_ptr<const PermissionSet> withheld =
@@ -1188,14 +1206,6 @@
}
void Dispatcher::InitOriginPermissions(const Extension* extension) {
- const GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
- WebSecurityPolicy::AddOriginAccessBlockListEntry(
- extension->url(), WebString::FromUTF8(webstore_launch_url.scheme()),
- WebString::FromUTF8(webstore_launch_url.host()), true);
-
- // TODO(devlin): Should we also block the webstore update URL here? See
- // https://crbug.com/826946 for a related instance.
-
UpdateOriginPermissions(*extension);
}
@@ -1213,24 +1223,61 @@
};
// Remove all old patterns associated with this extension.
- WebSecurityPolicy::ClearOriginAccessAllowListForOrigin(extension.url());
+ WebSecurityPolicy::ClearOriginAccessListForOrigin(extension.url());
delegate_->AddOriginAccessPermissions(extension,
IsExtensionActive(extension.id()));
- URLPatternSet patterns =
+ URLPatternSet origin_permissions =
extension.permissions_data()->GetEffectiveHostPermissions();
- for (size_t i = 0; i < arraysize(kSchemes); ++i) {
- const char* scheme = kSchemes[i];
- for (const auto& pattern : patterns) {
- if (pattern.MatchesScheme(scheme)) {
+ // Permissions declared by the extension.
+ for (const URLPattern& pattern : origin_permissions) {
+ for (const char* scheme : kSchemes) {
+ if (pattern.MatchesScheme(scheme))
WebSecurityPolicy::AddOriginAccessAllowListEntry(
extension.url(), WebString::FromUTF8(scheme),
- WebString::FromUTF8(pattern.host()), pattern.match_subdomains());
- }
+ WebString::FromUTF8(pattern.host()), pattern.match_subdomains(),
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
}
}
+
+ // Hosts blocked by enterprise policy.
+ for (const URLPattern& pattern :
+ extension.permissions_data()->policy_blocked_hosts()) {
+ for (const char* scheme : kSchemes) {
+ if (pattern.MatchesScheme(scheme))
+ WebSecurityPolicy::AddOriginAccessBlockListEntry(
+ extension.url(), WebString::FromUTF8(scheme),
+ WebString::FromUTF8(pattern.host()), pattern.match_subdomains(),
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ }
+ }
+
+ // Hosts exempted from the enterprise policy blocklist.
+ // This set intersection is necessary to prevent an enterprise policy from
+ // granting a host permission the extension didn't ask for.
+ URLPatternSet overlap = URLPatternSet::CreateIntersection(
+ extension.permissions_data()->policy_allowed_hosts(), origin_permissions,
+ URLPatternSet::IntersectionBehavior::kDetailed);
+ for (const URLPattern& pattern : overlap) {
+ for (const char* scheme : kSchemes) {
+ if (pattern.MatchesScheme(scheme))
+ WebSecurityPolicy::AddOriginAccessAllowListEntry(
+ extension.url(), WebString::FromUTF8(scheme),
+ WebString::FromUTF8(pattern.host()), pattern.match_subdomains(),
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+ }
+ };
+
+ const GURL webstore_launch_url = extension_urls::GetWebstoreLaunchURL();
+ WebSecurityPolicy::AddOriginAccessBlockListEntry(
+ extension.url(), WebString::FromUTF8(webstore_launch_url.scheme()),
+ WebString::FromUTF8(webstore_launch_url.host()), true,
+ network::mojom::CORSOriginAccessMatchPriority::kHighPriority);
+
+ // TODO(devlin): Should we also block the webstore update URL here? See
+ // https://crbug.com/826946 for a related instance.
}
void Dispatcher::EnableCustomElementWhiteList() {
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 155e1858..bc0164b3 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -18,6 +18,7 @@
#include "net/url_request/url_request.h"
#include "services/network/cors/cors_url_loader_factory.h"
#include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/cors.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/test/test_url_loader_client.h"
@@ -228,8 +229,9 @@
const std::string& protocol,
const std::string& domain,
bool allow_subdomains) {
- origin_access_list_.AddAllowListEntryForOrigin(source_origin, protocol,
- domain, allow_subdomains);
+ origin_access_list_.AddAllowListEntryForOrigin(
+ source_origin, protocol, domain, allow_subdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
}
static net::RedirectInfo CreateRedirectInfo(
diff --git a/services/network/public/cpp/cors/origin_access_entry.cc b/services/network/public/cpp/cors/origin_access_entry.cc
index 6b7400d..26361e4 100644
--- a/services/network/public/cpp/cors/origin_access_entry.cc
+++ b/services/network/public/cpp/cors/origin_access_entry.cc
@@ -30,12 +30,15 @@
} // namespace
-OriginAccessEntry::OriginAccessEntry(const std::string& protocol,
- const std::string& host,
- MatchMode match_mode)
+OriginAccessEntry::OriginAccessEntry(
+ const std::string& protocol,
+ const std::string& host,
+ MatchMode match_mode,
+ const network::mojom::CORSOriginAccessMatchPriority priority)
: protocol_(protocol),
host_(host),
match_mode_(match_mode),
+ priority_(priority),
host_is_ip_address_(url::HostIsIPAddress(host)),
host_is_public_suffix_(false) {
if (host_is_ip_address_)
diff --git a/services/network/public/cpp/cors/origin_access_entry.h b/services/network/public/cpp/cors/origin_access_entry.h
index 46a35c2..5052f1a 100644
--- a/services/network/public/cpp/cors/origin_access_entry.h
+++ b/services/network/public/cpp/cors/origin_access_entry.h
@@ -9,6 +9,7 @@
#include "base/component_export.h"
#include "base/macros.h"
+#include "services/network/public/mojom/cors.mojom-shared.h"
namespace url {
class Origin;
@@ -46,9 +47,14 @@
// will match all domains in the specified protocol.
// IPv6 addresses must include brackets (e.g.
// '[2001:db8:85a3::8a2e:370:7334]', not '2001:db8:85a3::8a2e:370:7334').
- OriginAccessEntry(const std::string& protocol,
- const std::string& host,
- MatchMode match_mode);
+ // The priority argument is used to break ties when multiple entries
+ // match.
+ OriginAccessEntry(
+ const std::string& protocol,
+ const std::string& host,
+ MatchMode match_mode,
+ const network::mojom::CORSOriginAccessMatchPriority priority =
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
OriginAccessEntry(OriginAccessEntry&& from);
// 'matchesOrigin' requires a protocol match (e.g. 'http' != 'https').
@@ -57,6 +63,9 @@
MatchResult MatchesDomain(const url::Origin& domain) const;
bool host_is_ip_address() const { return host_is_ip_address_; }
+ network::mojom::CORSOriginAccessMatchPriority priority() const {
+ return priority_;
+ }
const std::string& registerable_domain() const {
return registerable_domain_;
}
@@ -65,6 +74,7 @@
const std::string protocol_;
const std::string host_;
const MatchMode match_mode_;
+ network::mojom::CORSOriginAccessMatchPriority priority_;
const bool host_is_ip_address_;
std::string registerable_domain_;
diff --git a/services/network/public/cpp/cors/origin_access_entry_unittest.cc b/services/network/public/cpp/cors/origin_access_entry_unittest.cc
index d814028c..a68e978 100644
--- a/services/network/public/cpp/cors/origin_access_entry_unittest.cc
+++ b/services/network/public/cpp/cors/origin_access_entry_unittest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "services/network/public/cpp/cors/origin_access_entry.h"
+#include "services/network/public/mojom/cors.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
@@ -16,11 +17,15 @@
TEST(OriginAccessEntryTest, PublicSuffixListTest) {
url::Origin origin = url::Origin::Create(GURL("http://www.google.com"));
- OriginAccessEntry entry1("http", "google.com",
- OriginAccessEntry::kAllowSubdomains);
- OriginAccessEntry entry2("http", "hamster.com",
- OriginAccessEntry::kAllowSubdomains);
- OriginAccessEntry entry3("http", "com", OriginAccessEntry::kAllowSubdomains);
+ OriginAccessEntry entry1(
+ "http", "google.com", OriginAccessEntry::kAllowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ OriginAccessEntry entry2(
+ "http", "hamster.com", OriginAccessEntry::kAllowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ OriginAccessEntry entry3(
+ "http", "com", OriginAccessEntry::kAllowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_EQ(OriginAccessEntry::kMatchesOrigin, entry1.MatchesOrigin(origin));
EXPECT_EQ(OriginAccessEntry::kDoesNotMatchOrigin,
entry2.MatchesOrigin(origin));
@@ -86,8 +91,9 @@
SCOPED_TRACE(testing::Message()
<< "Host: " << test.host << ", Origin: " << test.origin);
url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
- OriginAccessEntry entry1(test.protocol, test.host,
- OriginAccessEntry::kAllowSubdomains);
+ OriginAccessEntry entry1(
+ test.protocol, test.host, OriginAccessEntry::kAllowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_EQ(test.expected_origin, entry1.MatchesOrigin(origin_to_test));
EXPECT_EQ(test.expected_domain, entry1.MatchesDomain(origin_to_test));
}
@@ -134,8 +140,9 @@
for (const auto& test : inputs) {
url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
- OriginAccessEntry entry1(test.protocol, test.host,
- OriginAccessEntry::kAllowRegisterableDomains);
+ OriginAccessEntry entry1(
+ test.protocol, test.host, OriginAccessEntry::kAllowRegisterableDomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
SCOPED_TRACE(testing::Message()
<< "Host: " << test.host << ", Origin: " << test.origin
@@ -186,8 +193,9 @@
for (const auto& test : inputs) {
url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
- OriginAccessEntry entry1(test.protocol, test.host,
- OriginAccessEntry::kAllowRegisterableDomains);
+ OriginAccessEntry entry1(
+ test.protocol, test.host, OriginAccessEntry::kAllowRegisterableDomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
SCOPED_TRACE(testing::Message()
<< "Host: " << test.host << ", Origin: " << test.origin
@@ -235,8 +243,9 @@
SCOPED_TRACE(testing::Message()
<< "Host: " << test.host << ", Origin: " << test.origin);
url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
- OriginAccessEntry entry1(test.protocol, test.host,
- OriginAccessEntry::kDisallowSubdomains);
+ OriginAccessEntry entry1(
+ test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_EQ(test.expected, entry1.MatchesOrigin(origin_to_test));
}
}
@@ -260,8 +269,9 @@
for (const auto& test : inputs) {
SCOPED_TRACE(testing::Message() << "Host: " << test.host);
- OriginAccessEntry entry(test.protocol, test.host,
- OriginAccessEntry::kDisallowSubdomains);
+ OriginAccessEntry entry(
+ test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_EQ(test.is_ip_address, entry.host_is_ip_address()) << test.host;
}
}
@@ -287,12 +297,14 @@
SCOPED_TRACE(testing::Message()
<< "Host: " << test.host << ", Origin: " << test.origin);
url::Origin origin_to_test = url::Origin::Create(GURL(test.origin));
- OriginAccessEntry entry1(test.protocol, test.host,
- OriginAccessEntry::kAllowSubdomains);
+ OriginAccessEntry entry1(
+ test.protocol, test.host, OriginAccessEntry::kAllowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_EQ(test.expected, entry1.MatchesOrigin(origin_to_test));
- OriginAccessEntry entry2(test.protocol, test.host,
- OriginAccessEntry::kDisallowSubdomains);
+ OriginAccessEntry entry2(
+ test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_EQ(test.expected, entry2.MatchesOrigin(origin_to_test));
}
}
diff --git a/services/network/public/cpp/cors/origin_access_list.cc b/services/network/public/cpp/cors/origin_access_list.cc
index 36b4e4a..150d9a0 100644
--- a/services/network/public/cpp/cors/origin_access_list.cc
+++ b/services/network/public/cpp/cors/origin_access_list.cc
@@ -14,15 +14,18 @@
void OriginAccessList::SetAllowListForOrigin(
const url::Origin& source_origin,
const std::vector<mojom::CorsOriginPatternPtr>& patterns) {
- SetForOrigin(source_origin, patterns, &allow_list_);
+ SetForOrigin(source_origin, patterns, &allow_list_,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
}
void OriginAccessList::AddAllowListEntryForOrigin(
const url::Origin& source_origin,
const std::string& protocol,
const std::string& domain,
- bool allow_subdomains) {
- AddForOrigin(source_origin, protocol, domain, allow_subdomains, &allow_list_);
+ bool allow_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
+ AddForOrigin(source_origin, protocol, domain, allow_subdomains, &allow_list_,
+ priority);
}
void OriginAccessList::ClearAllowList() {
@@ -32,15 +35,18 @@
void OriginAccessList::SetBlockListForOrigin(
const url::Origin& source_origin,
const std::vector<mojom::CorsOriginPatternPtr>& patterns) {
- SetForOrigin(source_origin, patterns, &block_list_);
+ SetForOrigin(source_origin, patterns, &block_list_,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
}
void OriginAccessList::AddBlockListEntryForOrigin(
const url::Origin& source_origin,
const std::string& protocol,
const std::string& domain,
- bool allow_subdomains) {
- AddForOrigin(source_origin, protocol, domain, allow_subdomains, &block_list_);
+ bool allow_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
+ AddForOrigin(source_origin, protocol, domain, allow_subdomains, &block_list_,
+ priority);
}
void OriginAccessList::ClearBlockList() {
@@ -53,15 +59,27 @@
return false;
std::string source = source_origin.Serialize();
url::Origin destination_origin = url::Origin::Create(destination);
- return IsInMapForOrigin(source, destination_origin, allow_list_) &&
- !IsInMapForOrigin(source, destination_origin, block_list_);
+ network::mojom::CORSOriginAccessMatchPriority allow_list_priority =
+ GetHighestPriorityOfRuleForOrigin(source, destination_origin,
+ allow_list_);
+ if (allow_list_priority ==
+ network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin)
+ return false;
+ network::mojom::CORSOriginAccessMatchPriority block_list_priority =
+ GetHighestPriorityOfRuleForOrigin(source, destination_origin,
+ block_list_);
+ if (block_list_priority ==
+ network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin)
+ return true;
+ return allow_list_priority > block_list_priority;
}
// static
void OriginAccessList::SetForOrigin(
const url::Origin& source_origin,
const std::vector<mojom::CorsOriginPatternPtr>& patterns,
- PatternMap* map) {
+ PatternMap* map,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
DCHECK(map);
DCHECK(!source_origin.opaque());
@@ -75,16 +93,19 @@
native_patterns.push_back(OriginAccessEntry(
pattern->protocol, pattern->domain,
pattern->allow_subdomains ? OriginAccessEntry::kAllowSubdomains
- : OriginAccessEntry::kDisallowSubdomains));
+ : OriginAccessEntry::kDisallowSubdomains,
+ priority));
}
}
// static
-void OriginAccessList::AddForOrigin(const url::Origin& source_origin,
- const std::string& protocol,
- const std::string& domain,
- bool allow_subdomains,
- PatternMap* map) {
+void OriginAccessList::AddForOrigin(
+ const url::Origin& source_origin,
+ const std::string& protocol,
+ const std::string& domain,
+ bool allow_subdomains,
+ PatternMap* map,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
DCHECK(map);
DCHECK(!source_origin.opaque());
@@ -92,23 +113,30 @@
(*map)[source].push_back(OriginAccessEntry(
protocol, domain,
allow_subdomains ? OriginAccessEntry::kAllowSubdomains
- : OriginAccessEntry::kDisallowSubdomains));
+ : OriginAccessEntry::kDisallowSubdomains,
+ priority));
}
// static
-bool OriginAccessList::IsInMapForOrigin(const std::string& source,
- const url::Origin& destination_origin,
- const PatternMap& map) {
+// TODO(nrpeter): Sort OriginAccessEntry entries on edit then we can return the
+// first match which will be the top priority.
+network::mojom::CORSOriginAccessMatchPriority
+OriginAccessList::GetHighestPriorityOfRuleForOrigin(
+ const std::string& source,
+ const url::Origin& destination_origin,
+ const PatternMap& map) {
+ network::mojom::CORSOriginAccessMatchPriority highest_priority =
+ network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin;
auto patterns_for_origin_it = map.find(source);
if (patterns_for_origin_it == map.end())
- return false;
+ return highest_priority;
for (const auto& entry : patterns_for_origin_it->second) {
if (entry.MatchesOrigin(destination_origin) !=
OriginAccessEntry::kDoesNotMatchOrigin) {
- return true;
+ highest_priority = std::max(highest_priority, entry.priority());
}
}
- return false;
+ return highest_priority;
}
} // namespace cors
diff --git a/services/network/public/cpp/cors/origin_access_list.h b/services/network/public/cpp/cors/origin_access_list.h
index 781b2367..0a8cffd 100644
--- a/services/network/public/cpp/cors/origin_access_list.h
+++ b/services/network/public/cpp/cors/origin_access_list.h
@@ -33,11 +33,14 @@
const std::vector<mojom::CorsOriginPatternPtr>& patterns);
// Adds a matching pattern for |protocol|, |domain|, and |allow_subdomains|
- // to the allow list.
- void AddAllowListEntryForOrigin(const url::Origin& source_origin,
- const std::string& protocol,
- const std::string& domain,
- bool allow_subdomains);
+ // to the allow list. When two or more entries in a list match the entry
+ // with the higher |priority| takes precedence.
+ void AddAllowListEntryForOrigin(
+ const url::Origin& source_origin,
+ const std::string& protocol,
+ const std::string& domain,
+ bool allow_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
// Clears the old allow list.
void ClearAllowList();
@@ -49,11 +52,14 @@
const std::vector<mojom::CorsOriginPatternPtr>& patterns);
// Adds a matching pattern for |protocol|, |domain|, and |allow_subdomains|
- // to the block list.
- void AddBlockListEntryForOrigin(const url::Origin& source_origin,
- const std::string& protocol,
- const std::string& domain,
- bool allow_subdomains);
+ // to the block list. When two or more entries in a list match the entry
+ // with the higher |priority| takes precedence.
+ void AddBlockListEntryForOrigin(
+ const url::Origin& source_origin,
+ const std::string& protocol,
+ const std::string& domain,
+ bool allow_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
// Clears the old block list.
void ClearBlockList();
@@ -70,15 +76,19 @@
static void SetForOrigin(
const url::Origin& source_origin,
const std::vector<mojom::CorsOriginPatternPtr>& patterns,
- PatternMap* map);
- static void AddForOrigin(const url::Origin& source_origin,
- const std::string& protocol,
- const std::string& domain,
- bool allow_subdomains,
- PatternMap* map);
- static bool IsInMapForOrigin(const std::string& source,
- const url::Origin& destination_origin,
- const PatternMap& map);
+ PatternMap* map,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
+ static void AddForOrigin(
+ const url::Origin& source_origin,
+ const std::string& protocol,
+ const std::string& domain,
+ bool allow_subdomains,
+ PatternMap* map,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
+ static network::mojom::CORSOriginAccessMatchPriority
+ GetHighestPriorityOfRuleForOrigin(const std::string& source,
+ const url::Origin& destination_origin,
+ const PatternMap& map);
PatternMap allow_list_;
PatternMap block_list_;
diff --git a/services/network/public/cpp/cors/origin_access_list_unittest.cc b/services/network/public/cpp/cors/origin_access_list_unittest.cc
index 8152944..7bab768 100644
--- a/services/network/public/cpp/cors/origin_access_list_unittest.cc
+++ b/services/network/public/cpp/cors/origin_access_list_unittest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "services/network/public/cpp/cors/origin_access_list.h"
+#include "services/network/public/mojom/cors.mojom.h"
#include <memory>
@@ -50,29 +51,35 @@
const std::string& host,
bool allow_subdomains) {
std::vector<mojom::CorsOriginPatternPtr> patterns;
- patterns.push_back(
- mojom::CorsOriginPattern::New(protocol, host, allow_subdomains));
+ patterns.push_back(mojom::CorsOriginPattern::New(
+ protocol, host, allow_subdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
list_.SetAllowListForOrigin(source_origin_, patterns);
}
- void AddAllowListEntry(const std::string& protocol,
- const std::string& host,
- bool allow_subdomains) {
+ void AddAllowListEntry(
+ const std::string& protocol,
+ const std::string& host,
+ bool allow_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
list_.AddAllowListEntryForOrigin(source_origin_, protocol, host,
- allow_subdomains);
+ allow_subdomains, priority);
}
void SetBlockListEntry(const std::string& protocol,
const std::string& host,
bool allow_subdomains) {
std::vector<mojom::CorsOriginPatternPtr> patterns;
- patterns.push_back(
- mojom::CorsOriginPattern::New(protocol, host, allow_subdomains));
+ patterns.push_back(mojom::CorsOriginPattern::New(
+ protocol, host, allow_subdomains,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority));
list_.SetBlockListForOrigin(source_origin_, patterns);
}
- void AddBlockListEntry(const std::string& protocol,
- const std::string& host,
- bool allow_subdomains) {
+ void AddBlockListEntry(
+ const std::string& protocol,
+ const std::string& host,
+ bool allow_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
list_.AddBlockListEntryForOrigin(source_origin_, protocol, host,
- allow_subdomains);
+ allow_subdomains, priority);
}
void ResetLists() {
std::vector<mojom::CorsOriginPatternPtr> patterns;
@@ -114,7 +121,9 @@
// Adding an entry that matches subdomains should grant access to any
// subdomains.
- AddAllowListEntry("https", "example.com", true);
+ AddAllowListEntry(
+ "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_TRUE(IsAllowed(https_example_origin()));
EXPECT_TRUE(IsAllowed(https_sub_example_origin()));
EXPECT_FALSE(IsAllowed(http_example_origin()));
@@ -139,12 +148,54 @@
TEST_F(OriginAccessListTest, IsAccessAllowedWildcardWithBlockListEntry) {
SetAllowListEntry("https", "", true);
- AddBlockListEntry("https", "google.com", false);
+ AddBlockListEntry(
+ "https", "google.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_TRUE(IsAllowed(https_example_origin()));
EXPECT_FALSE(IsAllowed(https_google_origin()));
}
+TEST_F(OriginAccessListTest, IsPriorityRespected) {
+ SetAllowListEntry("https", "example.com", true);
+ EXPECT_TRUE(IsAllowed(https_example_origin()));
+ EXPECT_TRUE(IsAllowed(https_sub_example_origin()));
+
+ // Higher priority blocklist overrides lower priority allowlist.
+ AddBlockListEntry(
+ "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ EXPECT_FALSE(IsAllowed(https_example_origin()));
+ EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+
+ // Higher priority allowlist overrides lower priority blocklist.
+ AddAllowListEntry(
+ "https", "example.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+ EXPECT_TRUE(IsAllowed(https_example_origin()));
+ EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+}
+
+TEST_F(OriginAccessListTest, IsPriorityRespectedReverse) {
+ AddAllowListEntry(
+ "https", "example.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+ EXPECT_TRUE(IsAllowed(https_example_origin()));
+ EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+
+ AddBlockListEntry(
+ "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ EXPECT_TRUE(IsAllowed(https_example_origin()));
+ EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+
+ AddAllowListEntry(
+ "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ EXPECT_TRUE(IsAllowed(https_example_origin()));
+ EXPECT_FALSE(IsAllowed(https_sub_example_origin()));
+}
+
} // namespace
} // namespace cors
diff --git a/services/network/public/mojom/cors.mojom b/services/network/public/mojom/cors.mojom
index 0e436e2..5199351 100644
--- a/services/network/public/mojom/cors.mojom
+++ b/services/network/public/mojom/cors.mojom
@@ -95,3 +95,13 @@
// Cross origin redirect location contains credentials such as 'user:pass'.
kRedirectContainsCredentials,
};
+
+// Determine which Cors exception takes precedence when multiple matches occur.
+enum CORSOriginAccessMatchPriority {
+ kNoMatchingOrigin,
+ kDefaultPriority,
+ kLowPriority,
+ kMediumPriority,
+ kHighPriority,
+ kMaxPriority
+};
diff --git a/services/network/public/mojom/cors_origin_pattern.mojom b/services/network/public/mojom/cors_origin_pattern.mojom
index 182abc8..a00e97cff 100644
--- a/services/network/public/mojom/cors_origin_pattern.mojom
+++ b/services/network/public/mojom/cors_origin_pattern.mojom
@@ -4,6 +4,8 @@
module network.mojom;
+import "services/network/public/mojom/cors.mojom";
+
// Parameters for representing a access origin whitelist or blacklist for CORS.
struct CorsOriginPattern {
// The protocol part of the destination URL.
@@ -14,4 +16,10 @@
// Whether subdomains match this protocol and host pattern.
bool allow_subdomains;
+
+ // Order of preference in which the pattern is applied. Higher priority
+ // patterns take precedence over lower ones. In the case were both a
+ // allow list and block list rule of the same priority match a request,
+ // the block list rule takes priority.
+ CORSOriginAccessMatchPriority priority;
};
diff --git a/third_party/blink/public/web/web_security_policy.h b/third_party/blink/public/web/web_security_policy.h
index 6e1673c..d155e6d 100644
--- a/third_party/blink/public/web/web_security_policy.h
+++ b/third_party/blink/public/web/web_security_policy.h
@@ -31,6 +31,7 @@
#ifndef THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_SECURITY_POLICY_H_
#define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_SECURITY_POLICY_H_
+#include "services/network/public/mojom/cors.mojom-shared.h"
#include "third_party/blink/public/platform/web_common.h"
#include "third_party/blink/public/platform/web_referrer_policy.h"
@@ -66,14 +67,32 @@
// Support for managing allow/block access lists to origins beyond the
// same-origin policy. The block list takes priority over the allow list.
+ // When an origin matches an entry on both the allow list and block list
+ // the entry with the higher priority defines whether access is allowed.
+ // In the case where both an allowlist and blocklist rule of the same
+ // priority match a request the blocklist rule takes priority.
+ // Callers should use
+ // network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority as the
+ // default priority unless overriding existing entries is explicitly needed.
BLINK_EXPORT static void AddOriginAccessAllowListEntry(
const WebURL& source_origin,
const WebString& destination_protocol,
const WebString& destination_host,
- bool allow_destination_subdomains);
+ bool allow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
BLINK_EXPORT static void ClearOriginAccessAllowListForOrigin(
const WebURL& source_origin);
BLINK_EXPORT static void ClearOriginAccessAllowList();
+ BLINK_EXPORT static void ClearOriginAccessListForOrigin(
+ const WebURL& source_origin);
+
+ BLINK_EXPORT static void AddOriginAccessBlockListEntry(
+ const WebURL& source_origin,
+ const WebString& destination_protocol,
+ const WebString& destination_host,
+ bool disallow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
+
BLINK_EXPORT static void AddOriginAccessBlockListEntry(
const WebURL& source_origin,
const WebString& destination_protocol,
diff --git a/third_party/blink/renderer/core/exported/web_security_policy.cc b/third_party/blink/renderer/core/exported/web_security_policy.cc
index 4ee1bb5..9ea3356 100644
--- a/third_party/blink/renderer/core/exported/web_security_policy.cc
+++ b/third_party/blink/renderer/core/exported/web_security_policy.cc
@@ -69,10 +69,11 @@
const WebURL& source_origin,
const WebString& destination_protocol,
const WebString& destination_host,
- bool allow_destination_subdomains) {
+ bool allow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
SecurityPolicy::AddOriginAccessAllowListEntry(
*SecurityOrigin::Create(source_origin), destination_protocol,
- destination_host, allow_destination_subdomains);
+ destination_host, allow_destination_subdomains, priority);
}
void WebSecurityPolicy::ClearOriginAccessAllowListForOrigin(
@@ -85,14 +86,23 @@
SecurityPolicy::ClearOriginAccessAllowList();
}
+void WebSecurityPolicy::ClearOriginAccessListForOrigin(
+ const WebURL& source_origin) {
+ scoped_refptr<SecurityOrigin> security_origin =
+ SecurityOrigin::Create(source_origin);
+ SecurityPolicy::ClearOriginAccessAllowListForOrigin(*security_origin);
+ SecurityPolicy::ClearOriginAccessBlockListForOrigin(*security_origin);
+}
+
void WebSecurityPolicy::AddOriginAccessBlockListEntry(
const WebURL& source_origin,
const WebString& destination_protocol,
const WebString& destination_host,
- bool allow_destination_subdomains) {
+ bool allow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
SecurityPolicy::AddOriginAccessBlockListEntry(
*SecurityOrigin::Create(source_origin), destination_protocol,
- destination_host, allow_destination_subdomains);
+ destination_host, allow_destination_subdomains, priority);
}
void WebSecurityPolicy::AddOriginTrustworthyWhiteList(const WebString& origin) {
diff --git a/third_party/blink/renderer/platform/weborigin/DEPS b/third_party/blink/renderer/platform/weborigin/DEPS
index 46fd51d..8b067e4 100644
--- a/third_party/blink/renderer/platform/weborigin/DEPS
+++ b/third_party/blink/renderer/platform/weborigin/DEPS
@@ -11,6 +11,7 @@
"+net/base",
"+services/network/public/cpp/cors/origin_access_entry.h",
"+services/network/public/cpp/cors/origin_access_list.h",
+ "+services/network/public/mojom/cors.mojom-shared.h",
"+services/network/public/mojom/cors_origin_pattern.mojom-shared.h",
"+third_party/blink/renderer/platform/blob/blob_url.h",
"+third_party/blink/renderer/platform/platform_export.h",
diff --git a/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc b/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc
index 9e729629..bba1484e 100644
--- a/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc
+++ b/third_party/blink/renderer/platform/weborigin/origin_access_entry.cc
@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/platform/weborigin/origin_access_entry.h"
+#include "services/network/public/mojom/cors.mojom-shared.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
namespace blink {
@@ -37,10 +38,12 @@
OriginAccessEntry::OriginAccessEntry(
const String& protocol,
const String& host,
- network::cors::OriginAccessEntry::MatchMode match_mode)
+ network::cors::OriginAccessEntry::MatchMode match_mode,
+ network::mojom::CORSOriginAccessMatchPriority priority)
: private_(std::string(protocol.Utf8().data()),
std::string(host.Utf8().data()),
- match_mode) {}
+ match_mode,
+ priority) {}
OriginAccessEntry::OriginAccessEntry(OriginAccessEntry&& from) = default;
diff --git a/third_party/blink/renderer/platform/weborigin/origin_access_entry.h b/third_party/blink/renderer/platform/weborigin/origin_access_entry.h
index 037cae5..7b7ae44 100644
--- a/third_party/blink/renderer/platform/weborigin/origin_access_entry.h
+++ b/third_party/blink/renderer/platform/weborigin/origin_access_entry.h
@@ -49,9 +49,14 @@
// will match all domains in the specified protocol.
// IPv6 addresses must include brackets (e.g.
// '[2001:db8:85a3::8a2e:370:7334]', not '2001:db8:85a3::8a2e:370:7334').
- OriginAccessEntry(const String& protocol,
- const String& host,
- network::cors::OriginAccessEntry::MatchMode);
+ // An entry with a higher priority will win in case there are two conflicting
+ // entries.
+ OriginAccessEntry(
+ const String& protocol,
+ const String& host,
+ network::cors::OriginAccessEntry::MatchMode,
+ network::mojom::CORSOriginAccessMatchPriority priority =
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
OriginAccessEntry(OriginAccessEntry&& from);
// 'matchesOrigin' requires a protocol match (e.g. 'http' != 'https').
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
index a7c8a3f..3604648 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_origin_test.cc
@@ -32,6 +32,7 @@
#include <stdint.h>
+#include "services/network/public/mojom/cors.mojom-shared.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/blob/blob_url.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
@@ -348,18 +349,110 @@
}
}
-TEST_F(SecurityOriginTest, CanRequestWithWhitelistedAccess) {
+TEST_F(SecurityOriginTest, CanRequestWithAllowListedAccess) {
scoped_refptr<const SecurityOrigin> origin =
SecurityOrigin::CreateFromString("https://chromium.org");
const blink::KURL url("https://example.com");
EXPECT_FALSE(origin->CanRequest(url));
- // Adding the url to the access whitelist should allow the request.
- SecurityPolicy::AddOriginAccessAllowListEntry(*origin, "https", "example.com",
- false);
+ // Adding the url to the access allowlist should allow the request.
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *origin, "https", "example.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
EXPECT_TRUE(origin->CanRequest(url));
}
+TEST_F(SecurityOriginTest, CannotRequestWithBlockListedAccess) {
+ scoped_refptr<const SecurityOrigin> origin =
+ SecurityOrigin::CreateFromString("https://chromium.org");
+ const blink::KURL allowed_url("https://test.example.com");
+ const blink::KURL blocked_url("https://example.com");
+
+ // BlockList that is more or same specificity wins.
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *origin, "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ SecurityPolicy::AddOriginAccessBlockListEntry(
+ *origin, "https", "example.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ // Block since example.com is on the allowlist & blocklist.
+ EXPECT_FALSE(origin->CanRequest(blocked_url));
+ // Allow since *.example.com is on the allowlist but not the blocklist.
+ EXPECT_TRUE(origin->CanRequest(allowed_url));
+}
+
+TEST_F(SecurityOriginTest, CanRequestWithMoreSpecificAllowList) {
+ scoped_refptr<const SecurityOrigin> origin =
+ SecurityOrigin::CreateFromString("https://chromium.org");
+ const blink::KURL allowed_url("https://test.example.com");
+ const blink::KURL blocked_url("https://example.com");
+
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *origin, "https", "test.example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+ SecurityPolicy::AddOriginAccessBlockListEntry(
+ *origin, "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ // Allow since test.example.com (allowlist) has a higher priority than
+ // *.example.com (blocklist).
+ EXPECT_TRUE(origin->CanRequest(allowed_url));
+ // Block since example.com isn't on the allowlist.
+ EXPECT_FALSE(origin->CanRequest(blocked_url));
+}
+
+TEST_F(SecurityOriginTest, PunycodeNotUnicode) {
+ scoped_refptr<const SecurityOrigin> origin =
+ SecurityOrigin::CreateFromString("https://chromium.org");
+ const blink::KURL unicode_url("https://☃.net/");
+ const blink::KURL punycode_url("https://xn--n3h.net/");
+
+ // Sanity check: Origin blocked by default.
+ EXPECT_FALSE(origin->CanRequest(punycode_url));
+ EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+ // Verify unicode origin can not be allowlisted.
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *origin, "https", "☃.net", true,
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+ EXPECT_FALSE(origin->CanRequest(punycode_url));
+ EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+ // Verify punycode allowlist only affects punycode URLs.
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *origin, "https", "xn--n3h.net", true,
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+ EXPECT_TRUE(origin->CanRequest(punycode_url));
+ EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+ // Clear enterprise policy allowlist.
+ SecurityPolicy::ClearOriginAccessAllowListForOrigin(*origin);
+
+ EXPECT_FALSE(origin->CanRequest(punycode_url));
+ EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+ // Simulate <all_urls> being in the extension permissions.
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *origin, "https", "", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+
+ EXPECT_TRUE(origin->CanRequest(punycode_url));
+ EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+ // Verify unicode origin can not be blocklisted.
+ SecurityPolicy::AddOriginAccessBlockListEntry(
+ *origin, "https", "☃.net", true,
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ EXPECT_TRUE(origin->CanRequest(punycode_url));
+ EXPECT_FALSE(origin->CanRequest(unicode_url));
+
+ // Verify punycode blocklist only affects punycode URLs.
+ SecurityPolicy::AddOriginAccessBlockListEntry(
+ *origin, "https", "xn--n3h.net", true,
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ EXPECT_FALSE(origin->CanRequest(punycode_url));
+ EXPECT_FALSE(origin->CanRequest(unicode_url));
+}
+
TEST_F(SecurityOriginTest, PortAndEffectivePortMethod) {
struct TestCase {
unsigned short port;
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy.cc b/third_party/blink/renderer/platform/weborigin/security_policy.cc
index 5374e03..1aef3f2 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_policy.cc
@@ -239,11 +239,13 @@
const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
- bool allow_destination_subdomains) {
+ bool allow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
MutexLocker lock(GetMutex());
GetOriginAccessList().AddAllowListEntryForOrigin(
source_origin.ToUrlOrigin(), WebString(destination_protocol).Utf8(),
- WebString(destination_domain).Utf8(), allow_destination_subdomains);
+ WebString(destination_domain).Utf8(), allow_destination_subdomains,
+ priority);
}
void SecurityPolicy::ClearOriginAccessAllowListForOrigin(
@@ -254,6 +256,14 @@
std::vector<network::mojom::CorsOriginPatternPtr>());
}
+void SecurityPolicy::ClearOriginAccessBlockListForOrigin(
+ const SecurityOrigin& source_origin) {
+ MutexLocker lock(GetMutex());
+ GetOriginAccessList().SetBlockListForOrigin(
+ source_origin.ToUrlOrigin(),
+ std::vector<network::mojom::CorsOriginPatternPtr>());
+}
+
void SecurityPolicy::ClearOriginAccessAllowList() {
MutexLocker lock(GetMutex());
GetOriginAccessList().ClearAllowList();
@@ -263,11 +273,13 @@
const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
- bool allow_destination_subdomains) {
+ bool allow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority) {
MutexLocker lock(GetMutex());
GetOriginAccessList().AddBlockListEntryForOrigin(
source_origin.ToUrlOrigin(), WebString(destination_protocol).Utf8(),
- WebString(destination_domain).Utf8(), allow_destination_subdomains);
+ WebString(destination_domain).Utf8(), allow_destination_subdomains,
+ priority);
}
void SecurityPolicy::ClearOriginAccessBlockList() {
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy.h b/third_party/blink/renderer/platform/weborigin/security_policy.h
index 5cf330b..68aea8f 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy.h
+++ b/third_party/blink/renderer/platform/weborigin/security_policy.h
@@ -29,6 +29,7 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_WEBORIGIN_SECURITY_POLICY_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WEBORIGIN_SECURITY_POLICY_H_
+#include "services/network/public/mojom/cors.mojom-shared.h"
#include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/blink/renderer/platform/weborigin/referrer.h"
#include "third_party/blink/renderer/platform/weborigin/referrer_policy.h"
@@ -65,18 +66,24 @@
const KURL&,
const String& referrer);
- static void AddOriginAccessAllowListEntry(const SecurityOrigin& source_origin,
- const String& destination_protocol,
- const String& destination_domain,
- bool allow_destination_subdomains);
+ static void AddOriginAccessAllowListEntry(
+ const SecurityOrigin& source_origin,
+ const String& destination_protocol,
+ const String& destination_domain,
+ bool allow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
static void ClearOriginAccessAllowListForOrigin(
const SecurityOrigin& source_origin);
+ static void ClearOriginAccessBlockListForOrigin(
+ const SecurityOrigin& source_origin);
static void ClearOriginAccessAllowList();
- static void AddOriginAccessBlockListEntry(const SecurityOrigin& source_origin,
- const String& destination_protocol,
- const String& destination_domain,
- bool allow_destination_subdomains);
+ static void AddOriginAccessBlockListEntry(
+ const SecurityOrigin& source_origin,
+ const String& destination_protocol,
+ const String& destination_domain,
+ bool allow_destination_subdomains,
+ const network::mojom::CORSOriginAccessMatchPriority priority);
static void ClearOriginAccessBlockList();
static bool IsOriginAccessAllowed(const SecurityOrigin* active_origin,
diff --git a/third_party/blink/renderer/platform/weborigin/security_policy_test.cc b/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
index e38c0b5..6faa417 100644
--- a/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
+++ b/third_party/blink/renderer/platform/weborigin/security_policy_test.cc
@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
+#include "services/network/public/mojom/cors.mojom-shared.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
@@ -349,8 +350,9 @@
// Adding access for https://example.com should work, but should not grant
// access to subdomains or other schemes.
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
- "https", "example.com", false);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "example.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
https_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(
@@ -369,8 +371,9 @@
// Adding an entry that matches subdomains should grant access to any
// subdomains.
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
- "https", "example.com", true);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
https_example_origin()));
EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(
@@ -381,8 +384,9 @@
TEST_F(SecurityPolicyAccessTest, IsOriginAccessAllowedWildCard) {
// An empty domain that matches subdomains results in matching every domain.
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
- "https", "", true);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
https_example_origin()));
EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
@@ -393,10 +397,12 @@
TEST_F(SecurityPolicyAccessTest, IsOriginAccessAllowedWithBlockListEntry) {
// The block list takes priority over the allow list.
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
- "https", "example.com", true);
- SecurityPolicy::AddOriginAccessBlockListEntry(*https_chromium_origin(),
- "https", "example.com", false);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ SecurityPolicy::AddOriginAccessBlockListEntry(
+ *https_chromium_origin(), "https", "example.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
https_example_origin()));
@@ -406,10 +412,12 @@
TEST_F(SecurityPolicyAccessTest,
IsOriginAccessAllowedWildcardWithBlockListEntry) {
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
- "https", "", true);
- SecurityPolicy::AddOriginAccessBlockListEntry(*https_chromium_origin(),
- "https", "google.com", false);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ SecurityPolicy::AddOriginAccessBlockListEntry(
+ *https_chromium_origin(), "https", "google.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(https_chromium_origin(),
https_example_origin()));
@@ -418,12 +426,15 @@
}
TEST_F(SecurityPolicyAccessTest, ClearOriginAccessAllowListForOrigin) {
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
- "https", "example.com", true);
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_chromium_origin(),
- "https", "google.com", true);
- SecurityPolicy::AddOriginAccessAllowListEntry(*https_example_origin(),
- "https", "google.com", true);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "google.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_example_origin(), "https", "google.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority);
SecurityPolicy::ClearOriginAccessAllowListForOrigin(*https_chromium_origin());
@@ -435,4 +446,25 @@
https_google_origin()));
}
+TEST_F(SecurityPolicyAccessTest, IsOriginAccessAllowedPriority) {
+ EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(
+ https_chromium_origin(), https_sub_example_origin()));
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "sub.example.com", false,
+ network::mojom::CORSOriginAccessMatchPriority::kLowPriority);
+ EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(
+ https_chromium_origin(), https_sub_example_origin()));
+ SecurityPolicy::AddOriginAccessBlockListEntry(
+ *https_chromium_origin(), "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kMediumPriority);
+ EXPECT_FALSE(SecurityPolicy::IsOriginAccessAllowed(
+ https_chromium_origin(), https_sub_example_origin()));
+ SecurityPolicy::AddOriginAccessAllowListEntry(
+ *https_chromium_origin(), "https", "example.com", true,
+ network::mojom::CORSOriginAccessMatchPriority::kHighPriority);
+
+ EXPECT_TRUE(SecurityPolicy::IsOriginAccessAllowed(
+ https_chromium_origin(), https_sub_example_origin()));
+}
+
} // namespace blink