Reland: Communicate ExtensionSettings policy to renderers
Origional CL (2499493004) was rolled back by sheriff due to failing MSAN tests.
This initial patchset is a direct patch of 2499493004. Second patchset contains the fix.
-Communicate which hosts are runtime blocked to all renderers
-Blocks host permissions for specific hosts by specific extensions
-Tests via blocking content script injection
-Introduces new test class for use with ExtensionSettings policy
BUG=624649
Review-Url: https://codereview.chromium.org/2833843004
Cr-Commit-Position: refs/heads/master@{#466832}
diff --git a/chrome/browser/extensions/api/permissions/permissions_apitest.cc b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
index 3de54c7..910bf62 100644
--- a/chrome/browser/extensions/api/permissions/permissions_apitest.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
@@ -5,6 +5,7 @@
#include "chrome/browser/extensions/api/permissions/permissions_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_management_test_util.h"
+#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "components/policy/core/browser/browser_policy_connector.h"
@@ -33,21 +34,6 @@
}
};
-class ExtensionApiTestWithManagementPolicy : public ExtensionApiTest {
- public:
- void SetUpInProcessBrowserTestFixture() override {
- ExtensionApiTest::SetUpInProcessBrowserTestFixture();
- EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
- .WillRepeatedly(testing::Return(true));
- policy_provider_.SetAutoRefresh();
- policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
- &policy_provider_);
- }
-
- protected:
- policy::MockConfigurationPolicyProvider policy_provider_;
-};
-
IN_PROC_BROWSER_TEST_F(ExtensionApiTest, PermissionsFail) {
ASSERT_TRUE(RunExtensionTest("permissions/disabled")) << message_;
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index 251ec77..839c2f97 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -13,7 +13,9 @@
#include "build/build_config.h"
#include "chrome/browser/extensions/api/permissions/permissions_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_management_test_util.h"
#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
#include "chrome/browser/extensions/test_extension_dir.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -503,6 +505,18 @@
ASSERT_TRUE(RunExtensionTest("content_scripts/permissions")) << message_;
}
+IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy,
+ ContentScriptPolicy) {
+ // Set enterprise policy to block injection to policy specified host.
+ {
+ ExtensionManagementPolicyUpdater pref(&policy_provider_);
+ pref.AddRuntimeBlockedHost("*", "*://example.com/*");
+ }
+ host_resolver()->AddRule("*.com", "127.0.0.1");
+ ASSERT_TRUE(StartEmbeddedTestServer());
+ ASSERT_TRUE(RunExtensionTest("content_scripts/policy")) << message_;
+}
+
IN_PROC_BROWSER_TEST_P(ContentScriptApiTest, ContentScriptBypassPageCSP) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionTest("content_scripts/bypass_page_csp")) << message_;
diff --git a/chrome/browser/extensions/extension_management.cc b/chrome/browser/extensions/extension_management.cc
index c564797..559efe8a 100644
--- a/chrome/browser/extensions/extension_management.cc
+++ b/chrome/browser/extensions/extension_management.cc
@@ -198,6 +198,16 @@
return default_settings_->blocked_permissions;
}
+const URLPatternSet& ExtensionManagement::GetDefaultRuntimeBlockedHosts()
+ const {
+ return default_settings_->runtime_blocked_hosts;
+}
+
+const URLPatternSet& ExtensionManagement::GetDefaultRuntimeAllowedHosts()
+ const {
+ return default_settings_->runtime_allowed_hosts;
+}
+
const URLPatternSet& ExtensionManagement::GetRuntimeBlockedHosts(
const Extension* extension) const {
auto iter_id = settings_by_id_.find(extension->id());
@@ -214,8 +224,13 @@
return default_settings_->runtime_allowed_hosts;
}
-bool ExtensionManagement::IsBlockedHost(const Extension* extension,
- const GURL& url) const {
+bool ExtensionManagement::UsesDefaultRuntimeHostRestrictions(
+ const Extension* extension) const {
+ return settings_by_id_.find(extension->id()) == settings_by_id_.end();
+}
+
+bool ExtensionManagement::IsRuntimeBlockedHost(const Extension* extension,
+ const GURL& url) const {
auto iter_id = settings_by_id_.find(extension->id());
if (iter_id != settings_by_id_.end())
return iter_id->second->runtime_blocked_hosts.MatchesURL(url);
diff --git a/chrome/browser/extensions/extension_management.h b/chrome/browser/extensions/extension_management.h
index 1dca6061..719c779 100644
--- a/chrome/browser/extensions/extension_management.h
+++ b/chrome/browser/extensions/extension_management.h
@@ -117,12 +117,30 @@
// Returns the list of hosts blocked by policy for |extension|.
const URLPatternSet& GetRuntimeBlockedHosts(const Extension* extension) const;
- // Returns the list of hosts |extension| is limited to by policy.
+ // Returns the hosts exempted by policy from the RuntimeBlockedHosts for
+ // |extension|.
const URLPatternSet& GetRuntimeAllowedHosts(const Extension* extension) const;
+ // Returns the list of hosts blocked by policy for Default scope. This can be
+ // overridden by an invividual scope which is queried via
+ // GetRuntimeBlockedHosts.
+ const URLPatternSet& GetDefaultRuntimeBlockedHosts() const;
+
+ // Returns the hosts exempted by policy from RuntimeBlockedHosts for
+ // the default scope. This can be overridden by an individual scope which is
+ // queries via GetRuntimeAllowedHosts. This should only be used to
+ // initialize a new renderer.
+ const URLPatternSet& GetDefaultRuntimeAllowedHosts() const;
+
+ // Checks if an |extension| has its own runtime_blocked_hosts or
+ // runtime_allowed_hosts defined in the individual scope of the
+ // ExtensionSettings policy.
+ // Returns false if an individual scoped setting isn't defined.
+ bool UsesDefaultRuntimeHostRestrictions(const Extension* extension) const;
+
// Checks if a URL is on the blocked host permissions list for a specific
// extension.
- bool IsBlockedHost(const Extension* extension, const GURL& url) const;
+ bool IsRuntimeBlockedHost(const Extension* extension, const GURL& url) const;
// Returns blocked permission set for |extension|.
std::unique_ptr<const PermissionSet> GetBlockedPermissions(
diff --git a/chrome/browser/extensions/extension_management_constants.cc b/chrome/browser/extensions/extension_management_constants.cc
index 11747e58..29e438b8 100644
--- a/chrome/browser/extensions/extension_management_constants.cc
+++ b/chrome/browser/extensions/extension_management_constants.cc
@@ -22,6 +22,7 @@
const char kRuntimeBlockedHosts[] = "runtime_blocked_hosts";
const char kRuntimeAllowedHosts[] = "runtime_allowed_hosts";
+const size_t kMaxItemsURLPatternSet = 100;
const char kUpdateUrl[] = "update_url";
const char kInstallSources[] = "install_sources";
diff --git a/chrome/browser/extensions/extension_management_constants.h b/chrome/browser/extensions/extension_management_constants.h
index a548572..3ab1cb2d 100644
--- a/chrome/browser/extensions/extension_management_constants.h
+++ b/chrome/browser/extensions/extension_management_constants.h
@@ -27,6 +27,7 @@
extern const char kRuntimeBlockedHosts[];
extern const char kRuntimeAllowedHosts[];
+extern const size_t kMaxItemsURLPatternSet;
extern const char kUpdateUrl[];
extern const char kInstallSources[];
diff --git a/chrome/browser/extensions/extension_management_internal.cc b/chrome/browser/extensions/extension_management_internal.cc
index a2596ae..cab0415b 100644
--- a/chrome/browser/extensions/extension_management_internal.cc
+++ b/chrome/browser/extensions/extension_management_internal.cc
@@ -115,6 +115,14 @@
// Get the list of URLPatterns.
if (dict->GetListWithoutPathExpansion(key,
&host_list_value)) {
+ if (host_list_value->GetSize() >
+ schema_constants::kMaxItemsURLPatternSet) {
+ LOG(WARNING) << "Exceeded maximum number of URL match patterns ("
+ << schema_constants::kMaxItemsURLPatternSet
+ << ") for attribute '" << key << "'";
+ return false;
+ }
+
out_value->ClearPatterns();
const int extension_scheme_mask =
URLPattern::GetValidSchemeMaskForExtensions();
@@ -122,7 +130,8 @@
std::string unparsed_str;
host_list_value->GetString(i, &unparsed_str);
URLPattern pattern = URLPattern(extension_scheme_mask);
- URLPattern::ParseResult parse_result = pattern.Parse(unparsed_str);
+ URLPattern::ParseResult parse_result = pattern.Parse(
+ unparsed_str, URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD);
if (parse_result != URLPattern::PARSE_SUCCESS) {
LOG(WARNING) << kMalformedPreferenceWarning;
LOG(WARNING) << "Invalid URL pattern '" + unparsed_str +
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index b74712c..42205f6 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -1255,6 +1255,21 @@
extensions::ExtensionManagement* management =
extensions::ExtensionManagementFactory::GetForBrowserContext(profile());
+ extensions::PermissionsUpdater(profile()).SetDefaultPolicyHostRestrictions(
+ management->GetDefaultRuntimeBlockedHosts(),
+ management->GetDefaultRuntimeAllowedHosts());
+ for (const auto& extension : registry_->enabled_extensions()) {
+ bool uses_default =
+ management->UsesDefaultRuntimeHostRestrictions(extension.get());
+ if (uses_default) {
+ extensions::PermissionsUpdater(profile()).SetUsesDefaultHostRestrictions(
+ extension.get());
+ } else {
+ extensions::PermissionsUpdater(profile()).SetPolicyHostRestrictions(
+ extension.get(), management->GetRuntimeBlockedHosts(extension.get()),
+ management->GetRuntimeAllowedHosts(extension.get()));
+ }
+ }
// Loop through the disabled extension list, find extensions to re-enable
// automatically. These extensions are exclusive from the |to_disable| and
diff --git a/chrome/browser/extensions/extension_with_management_policy_apitest.cc b/chrome/browser/extensions/extension_with_management_policy_apitest.cc
new file mode 100644
index 0000000..8ffaef6
--- /dev/null
+++ b/chrome/browser/extensions/extension_with_management_policy_apitest.cc
@@ -0,0 +1,21 @@
+// 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/extension_with_management_policy_apitest.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "net/test/embedded_test_server/http_request.h"
+
+ExtensionApiTestWithManagementPolicy::ExtensionApiTestWithManagementPolicy()
+ : ExtensionApiTest() {}
+
+ExtensionApiTestWithManagementPolicy::~ExtensionApiTestWithManagementPolicy() {}
+
+void ExtensionApiTestWithManagementPolicy::SetUpInProcessBrowserTestFixture() {
+ ExtensionApiTest::SetUpInProcessBrowserTestFixture();
+ EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
+ .WillRepeatedly(testing::Return(true));
+ policy_provider_.SetAutoRefresh();
+ policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+ &policy_provider_);
+}
diff --git a/chrome/browser/extensions/extension_with_management_policy_apitest.h b/chrome/browser/extensions/extension_with_management_policy_apitest.h
new file mode 100644
index 0000000..f0787f6
--- /dev/null
+++ b/chrome/browser/extensions/extension_with_management_policy_apitest.h
@@ -0,0 +1,29 @@
+// 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_WITH_MANAGEMENT_POLICY_APITEST_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_WITH_MANAGEMENT_POLICY_APITEST_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+
+// The ExtensionSettings policy affects host permissions which impacts several
+// API integration tests. This class enables easy declaration of
+// ExtensionSettings policies and functions commonly used during these tests.
+class ExtensionApiTestWithManagementPolicy : public ExtensionApiTest {
+ public:
+ ExtensionApiTestWithManagementPolicy();
+ ~ExtensionApiTestWithManagementPolicy() override;
+ void SetUpInProcessBrowserTestFixture() override;
+
+ protected:
+ policy::MockConfigurationPolicyProvider policy_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionApiTestWithManagementPolicy);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_WITH_MANAGEMENT_POLICY_APITEST_H_
diff --git a/chrome/browser/extensions/permissions_updater.cc b/chrome/browser/extensions/permissions_updater.cc
index a26f0ea..67e3ccb 100644
--- a/chrome/browser/extensions/permissions_updater.cc
+++ b/chrome/browser/extensions/permissions_updater.cc
@@ -146,6 +146,38 @@
NotifyPermissionsUpdated(REMOVED, extension, to_remove);
}
+void PermissionsUpdater::SetPolicyHostRestrictions(
+ const Extension* extension,
+ const URLPatternSet& runtime_blocked_hosts,
+ const URLPatternSet& runtime_allowed_hosts) {
+ extension->permissions_data()->SetPolicyHostRestrictions(
+ runtime_blocked_hosts, runtime_allowed_hosts);
+
+ // Send notification to the currently running renderers of the runtime block
+ // hosts settings.
+ const PermissionSet perms;
+ NotifyPermissionsUpdated(POLICY, extension, perms);
+}
+
+void PermissionsUpdater::SetUsesDefaultHostRestrictions(
+ const Extension* extension) {
+ extension->permissions_data()->SetUsesDefaultHostRestrictions();
+ const PermissionSet perms;
+ NotifyPermissionsUpdated(POLICY, extension, perms);
+}
+
+void PermissionsUpdater::SetDefaultPolicyHostRestrictions(
+ const URLPatternSet& default_runtime_blocked_hosts,
+ const URLPatternSet& default_runtime_allowed_hosts) {
+ PermissionsData::SetDefaultPolicyHostRestrictions(
+ default_runtime_blocked_hosts, default_runtime_allowed_hosts);
+
+ // Send notification to the currently running renderers of the runtime block
+ // hosts settings.
+ NotifyDefaultPolicyHostRestrictionsUpdated(default_runtime_blocked_hosts,
+ default_runtime_allowed_hosts);
+}
+
void PermissionsUpdater::RemovePermissionsUnsafe(
const Extension* extension,
const PermissionSet& to_remove) {
@@ -257,28 +289,31 @@
const Extension* extension,
const PermissionSet& changed) {
DCHECK_EQ(0, init_flag_ & INIT_FLAG_TRANSIENT);
- if (changed.IsEmpty())
+
+ if (changed.IsEmpty() && event_type != POLICY)
return;
UpdatedExtensionPermissionsInfo::Reason reason;
- events::HistogramValue histogram_value;
+ events::HistogramValue histogram_value = events::UNKNOWN;
const char* event_name = NULL;
+ Profile* profile = Profile::FromBrowserContext(browser_context_);
if (event_type == REMOVED) {
reason = UpdatedExtensionPermissionsInfo::REMOVED;
histogram_value = events::PERMISSIONS_ON_REMOVED;
event_name = permissions::OnRemoved::kEventName;
- } else {
- CHECK_EQ(ADDED, event_type);
+ } else if (event_type == ADDED) {
reason = UpdatedExtensionPermissionsInfo::ADDED;
histogram_value = events::PERMISSIONS_ON_ADDED;
event_name = permissions::OnAdded::kEventName;
+ } else {
+ DCHECK_EQ(POLICY, event_type);
+ reason = UpdatedExtensionPermissionsInfo::POLICY;
}
// Notify other APIs or interested parties.
- UpdatedExtensionPermissionsInfo info = UpdatedExtensionPermissionsInfo(
- extension, changed, reason);
- Profile* profile = Profile::FromBrowserContext(browser_context_);
+ UpdatedExtensionPermissionsInfo info =
+ UpdatedExtensionPermissionsInfo(extension, changed, reason);
content::NotificationService::current()->Notify(
extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
content::Source<Profile>(profile),
@@ -290,6 +325,14 @@
extension->permissions_data()->active_permissions());
params.withheld_permissions = ExtensionMsg_PermissionSetStruct(
extension->permissions_data()->withheld_permissions());
+ params.uses_default_policy_host_restrictions =
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions();
+ if (!params.uses_default_policy_host_restrictions) {
+ params.policy_blocked_hosts =
+ extension->permissions_data()->policy_blocked_hosts();
+ params.policy_allowed_hosts =
+ extension->permissions_data()->policy_allowed_hosts();
+ }
// Send the new permissions to the renderers.
for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
@@ -301,8 +344,35 @@
}
}
- // Trigger the onAdded and onRemoved events in the extension.
- DispatchEvent(extension->id(), histogram_value, event_name, changed);
+ // Trigger the onAdded and onRemoved events in the extension. We explicitly
+ // don't do this for policy-related events.
+ if (event_name)
+ DispatchEvent(extension->id(), histogram_value, event_name, changed);
+}
+
+// Notify the renderers that extension policy (policy_blocked_hosts) is updated
+// and provide new set of hosts.
+void PermissionsUpdater::NotifyDefaultPolicyHostRestrictionsUpdated(
+ const URLPatternSet& default_runtime_blocked_hosts,
+ const URLPatternSet& default_runtime_allowed_hosts) {
+ DCHECK_EQ(0, init_flag_ & INIT_FLAG_TRANSIENT);
+
+ Profile* profile = Profile::FromBrowserContext(browser_context_);
+
+ ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params params;
+ params.default_policy_blocked_hosts = default_runtime_blocked_hosts;
+ params.default_policy_allowed_hosts = default_runtime_allowed_hosts;
+
+ // Send the new policy to the renderers.
+ for (RenderProcessHost::iterator host_iterator(
+ RenderProcessHost::AllHostsIterator());
+ !host_iterator.IsAtEnd(); host_iterator.Advance()) {
+ RenderProcessHost* host = host_iterator.GetCurrentValue();
+ if (profile->IsSameProfile(
+ Profile::FromBrowserContext(host->GetBrowserContext()))) {
+ host->Send(new ExtensionMsg_UpdateDefaultPolicyHostRestrictions(params));
+ }
+ }
}
} // namespace extensions
diff --git a/chrome/browser/extensions/permissions_updater.h b/chrome/browser/extensions/permissions_updater.h
index 249c704..2b8597e7 100644
--- a/chrome/browser/extensions/permissions_updater.h
+++ b/chrome/browser/extensions/permissions_updater.h
@@ -19,6 +19,7 @@
class Extension;
class PermissionSet;
+class URLPatternSet;
// Updates an Extension's active and granted permissions in persistent storage
// and notifies interested parties of the changes.
@@ -81,6 +82,21 @@
void RemovePermissionsUnsafe(const Extension* extension,
const PermissionSet& permissions);
+ // Sets list of hosts |extension| may not interact with (overrides default).
+ void SetPolicyHostRestrictions(const Extension* extension,
+ const URLPatternSet& runtime_blocked_hosts,
+ const URLPatternSet& runtime_allowed_hosts);
+
+ // Sets extension to use the default list of policy host restrictions.
+ void SetUsesDefaultHostRestrictions(const Extension* extension);
+
+ // Sets list of hosts extensions may not interact with. Extension specific
+ // exceptions to this default policy are defined with
+ // SetPolicyHostRestrictions.
+ void SetDefaultPolicyHostRestrictions(
+ const URLPatternSet& default_runtime_blocked_hosts,
+ const URLPatternSet& default_runtime_allowed_hosts);
+
// Returns the set of revokable permissions.
std::unique_ptr<const PermissionSet> GetRevokablePermissions(
const Extension* extension) const;
@@ -98,6 +114,7 @@
enum EventType {
ADDED,
REMOVED,
+ POLICY,
};
// Sets the |extension|'s active permissions to |active| and records the
@@ -123,6 +140,14 @@
const Extension* extension,
const PermissionSet& changed);
+ // Issues the relevant events, messages and notifications when the
+ // default scope management policy have changed.
+ // Specifically, this sends the ExtensionMsg_UpdateDefaultHostRestrictions
+ // IPC message.
+ void NotifyDefaultPolicyHostRestrictionsUpdated(
+ const URLPatternSet& default_runtime_blocked_hosts,
+ const URLPatternSet& default_runtime_allowed_hosts);
+
// The associated BrowserContext.
content::BrowserContext* browser_context_;
diff --git a/chrome/browser/extensions/permissions_updater_unittest.cc b/chrome/browser/extensions/permissions_updater_unittest.cc
index 67e31dfb..fd14606 100644
--- a/chrome/browser/extensions/permissions_updater_unittest.cc
+++ b/chrome/browser/extensions/permissions_updater_unittest.cc
@@ -271,6 +271,15 @@
APIPermissionSet(), ManifestPermissionSet(), set, URLPatternSet());
};
+ auto can_access_page =
+ [](scoped_refptr<const extensions::Extension> extension,
+ const GURL& document_url) -> bool {
+ PermissionsData::AccessType access =
+ extension.get()->permissions_data()->GetPageAccess(
+ extension.get(), document_url, -1, nullptr);
+ return access == PermissionsData::ACCESS_ALLOWED;
+ };
+
{
// Test revoking optional permissions.
ListBuilder optional_permissions;
@@ -346,6 +355,7 @@
// By default, all-hosts was withheld, so the extension shouldn't have
// access to any site (like foo.com).
const GURL kOrigin("http://foo.com");
+
EXPECT_FALSE(extension->permissions_data()
->active_permissions()
.HasExplicitAccessToOrigin(kOrigin));
@@ -381,6 +391,103 @@
.HasExplicitAccessToOrigin(kOrigin));
EXPECT_TRUE(updater.GetRevokablePermissions(extension.get())->IsEmpty());
}
+
+ {
+ // Make sure policy restriction updates update permission data.
+ URLPatternSet default_policy_blocked_hosts;
+ URLPatternSet default_policy_allowed_hosts;
+ URLPatternSet policy_blocked_hosts;
+ URLPatternSet policy_allowed_hosts;
+ ListBuilder optional_permissions;
+ ListBuilder required_permissions;
+ required_permissions.Append("tabs").Append("http://*/*");
+ scoped_refptr<const Extension> extension =
+ CreateExtensionWithOptionalPermissions(optional_permissions.Build(),
+ required_permissions.Build(),
+ "ExtensionSettings");
+ AddPattern(&default_policy_blocked_hosts, "http://*.google.com/*");
+ PermissionsUpdater updater(profile());
+ updater.InitializePermissions(extension.get());
+ extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+ default_policy_blocked_hosts, default_policy_allowed_hosts);
+
+ // By default, all subdomains of google.com should be blocked.
+ const GURL kOrigin("http://foo.com");
+ const GURL kGoogle("http://www.google.com");
+ const GURL kExampleGoogle("http://example.google.com");
+ EXPECT_TRUE(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+ EXPECT_TRUE(can_access_page(extension, kOrigin));
+ EXPECT_FALSE(can_access_page(extension, kGoogle));
+ EXPECT_FALSE(can_access_page(extension, kExampleGoogle));
+
+ AddPattern(&default_policy_allowed_hosts, "http://example.google.com/*");
+ // Give the extension access to example.google.com. Now the
+ // example.google.com should not be a runtime blocked host.
+ updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+ default_policy_allowed_hosts);
+
+ EXPECT_TRUE(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+ EXPECT_TRUE(can_access_page(extension, kOrigin));
+ EXPECT_FALSE(can_access_page(extension, kGoogle));
+ EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+ // Revoke extension access to foo.com. Now, foo.com should be a runtime
+ // blocked host.
+ AddPattern(&default_policy_blocked_hosts, "*://*.foo.com/");
+ updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+ default_policy_allowed_hosts);
+ EXPECT_TRUE(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+ EXPECT_FALSE(can_access_page(extension, kOrigin));
+ EXPECT_FALSE(can_access_page(extension, kGoogle));
+ EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+ // Remove foo.com from blocked hosts. The extension should no longer have
+ // be a runtime blocked host.
+ default_policy_blocked_hosts.ClearPatterns();
+ AddPattern(&default_policy_blocked_hosts, "*://*.foo.com/");
+ updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+ default_policy_allowed_hosts);
+ EXPECT_TRUE(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+ EXPECT_FALSE(can_access_page(extension, kOrigin));
+ EXPECT_TRUE(can_access_page(extension, kGoogle));
+ EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+ // Set an empty individual policy, should not affect default policy.
+ updater.SetPolicyHostRestrictions(extension.get(), policy_blocked_hosts,
+ policy_allowed_hosts);
+ EXPECT_FALSE(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+ EXPECT_TRUE(can_access_page(extension, kOrigin));
+ EXPECT_TRUE(can_access_page(extension, kGoogle));
+ EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+ // Block google.com for the Individual scope.
+ // Whitelist example.google.com for the Indiviaul scope.
+ // Leave google.com and example.google.com off both the whitelist and
+ // blacklist for Default scope.
+ AddPattern(&policy_blocked_hosts, "*://*.google.com/*");
+ AddPattern(&policy_allowed_hosts, "*://example.google.com/*");
+ updater.SetPolicyHostRestrictions(extension.get(), policy_blocked_hosts,
+ policy_allowed_hosts);
+ EXPECT_FALSE(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+ EXPECT_TRUE(can_access_page(extension, kOrigin));
+ EXPECT_FALSE(can_access_page(extension, kGoogle));
+ EXPECT_TRUE(can_access_page(extension, kExampleGoogle));
+
+ // Switch back to default scope for extension.
+ updater.SetUsesDefaultHostRestrictions(extension.get());
+ EXPECT_TRUE(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions());
+ default_policy_blocked_hosts.ClearPatterns();
+ default_policy_allowed_hosts.ClearPatterns();
+ updater.SetDefaultPolicyHostRestrictions(default_policy_blocked_hosts,
+ default_policy_allowed_hosts);
+ }
}
// Test that the permissions updater delegate works - in this test it removes
diff --git a/chrome/common/extensions/permissions/permissions_data_unittest.cc b/chrome/common/extensions/permissions/permissions_data_unittest.cc
index 5147af4..c86741d 100644
--- a/chrome/common/extensions/permissions/permissions_data_unittest.cc
+++ b/chrome/common/extensions/permissions/permissions_data_unittest.cc
@@ -15,6 +15,7 @@
#include "chrome/common/extensions/extension_test_util.h"
#include "components/crx_file/id_util.h"
#include "content/public/common/socket_permission_request.h"
+#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
@@ -224,11 +225,13 @@
extension->permissions_data()->UpdateTabSpecificPermissions(
1, PermissionSet(APIPermissionSet(), ManifestPermissionSet(), new_hosts,
URLPatternSet()));
- EXPECT_TRUE(extension->permissions_data()->GetEffectiveHostPermissions().
- MatchesURL(tab_url));
+ EXPECT_TRUE(
+ extension->permissions_data()->GetEffectiveHostPermissions().MatchesURL(
+ tab_url));
extension->permissions_data()->ClearTabSpecificPermissions(1);
- EXPECT_FALSE(extension->permissions_data()->GetEffectiveHostPermissions().
- MatchesURL(tab_url));
+ EXPECT_FALSE(
+ extension->permissions_data()->GetEffectiveHostPermissions().MatchesURL(
+ tab_url));
}
TEST(PermissionsDataTest, SocketPermissions) {
@@ -236,8 +239,8 @@
std::string error;
extension = LoadManifest("socket_permissions", "empty.json");
- EXPECT_FALSE(CheckSocketPermission(extension,
- SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(CheckSocketPermission(
+ extension, SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
extension = LoadManifestUnchecked("socket_permissions",
"socket1.json",
@@ -251,19 +254,18 @@
EXPECT_EQ(expected_error_msg_header, error);
extension = LoadManifest("socket_permissions", "socket2.json");
- EXPECT_TRUE(CheckSocketPermission(extension,
- SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
- EXPECT_FALSE(CheckSocketPermission(
- extension, SocketPermissionRequest::UDP_BIND, "", 80));
EXPECT_TRUE(CheckSocketPermission(
- extension, SocketPermissionRequest::UDP_BIND, "", 8888));
+ extension, SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(CheckSocketPermission(
+ extension, SocketPermissionRequest::UDP_BIND, "", 80));
+ EXPECT_TRUE(CheckSocketPermission(
+ extension, SocketPermissionRequest::UDP_BIND, "", 8888));
EXPECT_FALSE(CheckSocketPermission(
- extension, SocketPermissionRequest::UDP_SEND_TO, "example.com", 1900));
- EXPECT_TRUE(CheckSocketPermission(
- extension,
- SocketPermissionRequest::UDP_SEND_TO,
- "239.255.255.250", 1900));
+ extension, SocketPermissionRequest::UDP_SEND_TO, "example.com", 1900));
+ EXPECT_TRUE(CheckSocketPermission(extension,
+ SocketPermissionRequest::UDP_SEND_TO,
+ "239.255.255.250", 1900));
}
TEST(PermissionsDataTest, IsRestrictedUrl) {
@@ -381,6 +383,9 @@
: http_url("http://www.google.com"),
http_url_with_path("http://www.google.com/index.html"),
https_url("https://www.google.com"),
+ example_com("https://example.com"),
+ test_example_com("https://test.example.com"),
+ sample_example_com("https://sample.example.com"),
file_url("file:///foo/bar"),
favicon_url("chrome://favicon/http://www.google.com"),
extension_url("chrome-extension://" +
@@ -391,6 +396,9 @@
urls_.insert(http_url);
urls_.insert(http_url_with_path);
urls_.insert(https_url);
+ urls_.insert(example_com);
+ urls_.insert(test_example_com);
+ urls_.insert(sample_example_com);
urls_.insert(file_url);
urls_.insert(favicon_url);
urls_.insert(extension_url);
@@ -476,6 +484,9 @@
const GURL http_url;
const GURL http_url_with_path;
const GURL https_url;
+ const GURL example_com;
+ const GURL test_example_com;
+ const GURL sample_example_com;
const GURL file_url;
// We should allow host permission but not scripting permission for favicon
@@ -592,8 +603,8 @@
scoped_refptr<Extension> extension;
// Test <all_urls> for regular extensions.
- extension = LoadManifestStrict("script_and_capture",
- "extension_regular_all.json");
+ extension =
+ LoadManifestStrict("script_and_capture", "extension_regular_all.json");
EXPECT_TRUE(Allowed(extension.get(), http_url));
EXPECT_TRUE(Allowed(extension.get(), https_url));
EXPECT_TRUE(CaptureOnly(extension.get(), file_url));
@@ -616,8 +627,8 @@
EXPECT_TRUE(permissions_data->HasHostPermission(favicon_url));
// Test * for scheme, which implies just the http/https schemes.
- extension = LoadManifestStrict("script_and_capture",
- "extension_wildcard.json");
+ extension =
+ LoadManifestStrict("script_and_capture", "extension_wildcard.json");
EXPECT_TRUE(ScriptOnly(extension.get(), http_url));
EXPECT_TRUE(ScriptOnly(extension.get(), https_url));
EXPECT_TRUE(Blocked(extension.get(), settings_url));
@@ -645,21 +656,21 @@
// Having chrome://favicon/* should not give you chrome://*
extension = LoadManifestStrict("script_and_capture",
- "extension_chrome_favicon_wildcard.json");
+ "extension_chrome_favicon_wildcard.json");
EXPECT_TRUE(Blocked(extension.get(), settings_url));
EXPECT_TRUE(ScriptOnly(extension.get(), favicon_url));
EXPECT_TRUE(Blocked(extension.get(), about_url));
EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
// Having http://favicon should not give you chrome://favicon
- extension = LoadManifestStrict("script_and_capture",
- "extension_http_favicon.json");
+ extension =
+ LoadManifestStrict("script_and_capture", "extension_http_favicon.json");
EXPECT_TRUE(Blocked(extension.get(), settings_url));
EXPECT_TRUE(Blocked(extension.get(), favicon_url));
// Component extensions with <all_urls> should get everything.
extension = LoadManifest("script_and_capture", "extension_component_all.json",
- Manifest::COMPONENT, Extension::NO_FLAGS);
+ Manifest::COMPONENT, Extension::NO_FLAGS);
EXPECT_TRUE(Allowed(extension.get(), http_url));
EXPECT_TRUE(Allowed(extension.get(), https_url));
EXPECT_TRUE(Allowed(extension.get(), settings_url));
@@ -668,9 +679,9 @@
EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
// Component extensions should only get access to what they ask for.
- extension = LoadManifest("script_and_capture",
- "extension_component_google.json", Manifest::COMPONENT,
- Extension::NO_FLAGS);
+ extension =
+ LoadManifest("script_and_capture", "extension_component_google.json",
+ Manifest::COMPONENT, Extension::NO_FLAGS);
EXPECT_TRUE(ScriptOnly(extension.get(), http_url));
EXPECT_TRUE(Blocked(extension.get(), https_url));
EXPECT_TRUE(Blocked(extension.get(), file_url));
@@ -832,4 +843,164 @@
}
}
+TEST_F(ExtensionScriptAndCaptureVisibleTest, PolicyHostRestrictionsSwap) {
+ // Makes sure when an extension gets an individual policy for host
+ // restrictions it overrides the default policy. Also tests transitioning back
+ // to the default policy when an individual policy is removed.
+ URLPattern example_com_pattern =
+ URLPattern(URLPattern::SCHEME_ALL, "*://*.example.com/*");
+ URLPattern test_example_com_pattern =
+ URLPattern(URLPattern::SCHEME_ALL, "*://test.example.com/*");
+ URLPatternSet default_blocked;
+ URLPatternSet default_allowed;
+ default_blocked.AddPattern(example_com_pattern);
+ default_allowed.AddPattern(test_example_com_pattern);
+
+ // Test <all_urls> for regular extensions.
+ scoped_refptr<Extension> extension =
+ LoadManifestStrict("script_and_capture", "extension_regular_all.json");
+ extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+ default_blocked, default_allowed);
+
+ // The default policy applies to all extensions at this point. The extension
+ // should be able to access test.example.com but be blocked from
+ // accessing any other subdomains of example.com or example.com itself.
+ EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+ EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+ EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+
+ URLPatternSet blocked;
+ blocked.AddPattern(test_example_com_pattern);
+ URLPatternSet allowed;
+ extension->permissions_data()->SetPolicyHostRestrictions(blocked, allowed);
+
+ // We've applied an individual policy which overrides the default policy.
+ // The only URL that should be blocked is test.example.com.
+ EXPECT_TRUE(Allowed(extension.get(), example_com));
+ EXPECT_TRUE(Allowed(extension.get(), sample_example_com));
+ EXPECT_TRUE(CaptureOnly(extension.get(), test_example_com));
+
+ blocked.AddPattern(example_com_pattern);
+ allowed.AddPattern(test_example_com_pattern);
+ extension->permissions_data()->SetPolicyHostRestrictions(blocked, allowed);
+
+ // Adding example.com and all its subdomains to the blocked list and
+ // test.example.com to the whitelist. This is still the individual policy
+ // Since the whitelist overrides a blacklist we expect to allow access to
+ // test.example.com but block access to all other example.com subdomains
+ // (sample.example.com) and example.com itself.
+ EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+ EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+ EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+
+ blocked.ClearPatterns();
+ allowed.ClearPatterns();
+ extension->permissions_data()->SetPolicyHostRestrictions(blocked, allowed);
+
+ // Cleared all URLs from the individual policy, so all URLs should have
+ // access. We want to make sure that a block at the default level doesn't
+ // apply since we're still definining an individual policy.
+ EXPECT_TRUE(Allowed(extension.get(), example_com));
+ EXPECT_TRUE(Allowed(extension.get(), sample_example_com));
+ EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+
+ // Flip back to using default policy for this extension.
+ extension->permissions_data()->SetUsesDefaultHostRestrictions();
+
+ // Make sure the default policy has the same effect as before we defined an
+ // individual policy. Access to test.example.com should be allowed, but all
+ // other subdomains and example.com itself should be blocked.
+ EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+ EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+ EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+}
+
+TEST_F(ExtensionScriptAndCaptureVisibleTest, PolicyHostRestrictions) {
+ // Test that host restrictions applied by policy take effect on normal URLs,
+ // iframe urls, different schemes, and components.
+ URLPatternSet default_blocked;
+ URLPatternSet default_allowed;
+ default_blocked.AddPattern(
+ URLPattern(URLPattern::SCHEME_ALL, "https://*.example.com/*"));
+ default_allowed.AddPattern(
+ URLPattern(URLPattern::SCHEME_ALL, "https://test.example.com/*"));
+
+ // In all of these tests, test.example.com should have scripting allowed, with
+ // all other subdomains and example.com itself blocked.
+
+ // Test <all_urls> for regular extensions.
+ scoped_refptr<Extension> extension =
+ LoadManifestStrict("script_and_capture", "extension_regular_all.json");
+ extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+ default_blocked, default_allowed);
+
+ EXPECT_TRUE(Allowed(extension.get(), http_url));
+ EXPECT_TRUE(Allowed(extension.get(), https_url));
+ EXPECT_TRUE(CaptureOnly(extension.get(), example_com));
+ EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+ EXPECT_TRUE(CaptureOnly(extension.get(), sample_example_com));
+ EXPECT_TRUE(CaptureOnly(extension.get(), file_url));
+ EXPECT_TRUE(CaptureOnly(extension.get(), settings_url));
+ EXPECT_TRUE(CaptureOnly(extension.get(), favicon_url));
+ EXPECT_TRUE(CaptureOnly(extension.get(), about_url));
+ EXPECT_TRUE(CaptureOnly(extension.get(), extension_url));
+
+ // Test access to iframed content.
+ GURL within_extension_url = extension->GetResourceURL("page.html");
+ EXPECT_TRUE(AllowedScript(extension.get(), http_url));
+ EXPECT_TRUE(AllowedScript(extension.get(), http_url_with_path));
+ EXPECT_TRUE(BlockedScript(extension.get(), example_com));
+ EXPECT_TRUE(AllowedScript(extension.get(), test_example_com));
+ EXPECT_TRUE(BlockedScript(extension.get(), sample_example_com));
+ EXPECT_TRUE(AllowedScript(extension.get(), https_url));
+ EXPECT_TRUE(BlockedScript(extension.get(), within_extension_url));
+ EXPECT_TRUE(BlockedScript(extension.get(), extension_url));
+
+ // Supress host permission for example.com since its on the blocklist
+ EXPECT_FALSE(extension->permissions_data()->HasHostPermission(example_com));
+ // Allow host permission for test.example.com since its on the whitelist and
+ // blacklist. The whitelist overrides the blacklist.
+ EXPECT_TRUE(
+ extension->permissions_data()->HasHostPermission(test_example_com));
+ EXPECT_FALSE(extension->permissions_data()->HasHostPermission(settings_url));
+ EXPECT_FALSE(extension->permissions_data()->HasHostPermission(about_url));
+ EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
+
+ // Test * for scheme, which implies just the http/https schemes.
+ extension =
+ LoadManifestStrict("script_and_capture", "extension_wildcard.json");
+ extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+ default_blocked, default_allowed);
+ EXPECT_TRUE(ScriptOnly(extension.get(), http_url));
+ EXPECT_TRUE(Blocked(extension.get(), example_com));
+ EXPECT_TRUE(ScriptOnly(extension.get(), test_example_com));
+ EXPECT_TRUE(Blocked(extension.get(), sample_example_com));
+ EXPECT_TRUE(ScriptOnly(extension.get(), https_url));
+ EXPECT_TRUE(Blocked(extension.get(), settings_url));
+ EXPECT_TRUE(Blocked(extension.get(), about_url));
+ EXPECT_TRUE(Blocked(extension.get(), file_url));
+ EXPECT_TRUE(Blocked(extension.get(), favicon_url));
+ extension =
+ LoadManifest("script_and_capture", "extension_wildcard_settings.json");
+ extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+ default_blocked, default_allowed);
+ EXPECT_TRUE(Blocked(extension.get(), settings_url));
+
+ // Component extensions with <all_urls> should get everything regardless of
+ // policy.
+ extension = LoadManifest("script_and_capture", "extension_component_all.json",
+ Manifest::COMPONENT, Extension::NO_FLAGS);
+ extension->permissions_data()->SetDefaultPolicyHostRestrictions(
+ default_blocked, default_allowed);
+ EXPECT_TRUE(Allowed(extension.get(), http_url));
+ EXPECT_TRUE(Allowed(extension.get(), https_url));
+ EXPECT_TRUE(Allowed(extension.get(), example_com));
+ EXPECT_TRUE(Allowed(extension.get(), test_example_com));
+ EXPECT_TRUE(Allowed(extension.get(), sample_example_com));
+ EXPECT_TRUE(Allowed(extension.get(), settings_url));
+ EXPECT_TRUE(Allowed(extension.get(), about_url));
+ EXPECT_TRUE(Allowed(extension.get(), favicon_url));
+ EXPECT_TRUE(extension->permissions_data()->HasHostPermission(favicon_url));
+}
+
} // namespace extensions
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 05034c5..b149c1b1 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2072,6 +2072,8 @@
"../browser/extensions/extension_browsertest.h",
"../browser/extensions/extension_function_test_utils.cc",
"../browser/extensions/extension_function_test_utils.h",
+ "../browser/extensions/extension_with_management_policy_apitest.cc",
+ "../browser/extensions/extension_with_management_policy_apitest.h",
"../browser/extensions/updater/extension_cache_fake.cc",
"../browser/extensions/updater/extension_cache_fake.h",
"../browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_model_browsertest_win.cc",
diff --git a/chrome/test/data/extensions/api_test/content_scripts/policy/background.js b/chrome/test/data/extensions/api_test/content_scripts/policy/background.js
new file mode 100644
index 0000000..7376923
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/policy/background.js
@@ -0,0 +1,53 @@
+// 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.
+
+var pass = chrome.test.callbackPass;
+var callbackFail = chrome.test.callbackFail;
+var listenForever = chrome.test.listenForever;
+
+var port;
+
+function testUrl(domain) {
+ return 'http://' + domain + ':' + port +
+ '/extensions/test_file.html';
+}
+
+function error(domain) {
+ return 'Cannot access contents of url "' + testUrl(domain) + '".' +
+ ' Extension manifest must request permission to access this host.';
+}
+
+// Creates a new tab, navigated to the specified |domain|.
+function createTestTab(domain, callback) {
+ var createdTabId = -1;
+ var done = listenForever(
+ chrome.tabs.onUpdated,
+ function(tabId, changeInfo, tab) {
+ if (tabId == createdTabId && changeInfo.status != 'loading') {
+ callback(tab);
+ done();
+ }
+ });
+
+ chrome.tabs.create({url: testUrl(domain)}, pass(function(tab) {
+ createdTabId = tab.id;
+ }));
+}
+
+chrome.test.getConfig(function(config) {
+ port = config.testServer.port;
+ chrome.test.runTests([
+
+ // Make sure we can't inject a script into a policy blocked host.
+ function policyBlocksInjection() {
+ createTestTab('example.com', pass(function(tab) {
+ chrome.tabs.executeScript(
+ tab.id, {code: 'document.title = "success"'},
+ callbackFail(
+ 'This page cannot be scripted due to ' +
+ 'an ExtensionsSettings policy.'));
+ }));
+ },
+ ]);
+});
diff --git a/chrome/test/data/extensions/api_test/content_scripts/policy/manifest.json b/chrome/test/data/extensions/api_test/content_scripts/policy/manifest.json
new file mode 100644
index 0000000..48de7c0
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/content_scripts/policy/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "content_scripts/policy",
+ "version": "1",
+ "manifest_version": 2,
+ "permissions": ["tabs", "*://example.com/*"],
+ "background": { "scripts": ["background.js"] }
+}
diff --git a/extensions/browser/renderer_startup_helper.cc b/extensions/browser/renderer_startup_helper.cc
index 30bd75ee..55f6509 100644
--- a/extensions/browser/renderer_startup_helper.cc
+++ b/extensions/browser/renderer_startup_helper.cc
@@ -23,6 +23,7 @@
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/features/feature_session_type.h"
+#include "extensions/common/permissions/permissions_data.h"
#include "ui/base/webui/web_ui_util.h"
using content::BrowserContext;
@@ -123,6 +124,15 @@
WebViewGuest::GetPartitionID(process)));
}
+ // Load default policy_blocked_hosts and policy_allowed_hosts settings, part
+ // of the ExtensionSettings policy.
+ ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params params;
+ params.default_policy_blocked_hosts =
+ PermissionsData::default_policy_blocked_hosts();
+ params.default_policy_allowed_hosts =
+ PermissionsData::default_policy_allowed_hosts();
+ process->Send(new ExtensionMsg_UpdateDefaultPolicyHostRestrictions(params));
+
// Loaded extensions.
std::vector<ExtensionMsg_Loaded_Params> loaded_extensions;
BrowserContext* renderer_context = process->GetBrowserContext();
diff --git a/extensions/common/constants.cc b/extensions/common/constants.cc
index c569a2d..7770085 100644
--- a/extensions/common/constants.cc
+++ b/extensions/common/constants.cc
@@ -112,4 +112,8 @@
// Keep in sync with _api_features.json and _manifest_features.json.
};
+// Error returned when scripting of a page is denied due to enterprise policy.
+const char kPolicyBlockedScripting[] =
+ "This page cannot be scripted due to an ExtensionsSettings policy.";
+
} // namespace extension_misc
diff --git a/extensions/common/constants.h b/extensions/common/constants.h
index 587299f..06f7f6f 100644
--- a/extensions/common/constants.h
+++ b/extensions/common/constants.h
@@ -220,6 +220,9 @@
// Extension ids used by Hangouts.
extern const char* const kHangoutsExtensionIds[6];
+// Error message when enterprise policy blocks scripting of webpage.
+extern const char kPolicyBlockedScripting[];
+
} // namespace extension_misc
#endif // EXTENSIONS_COMMON_CONSTANTS_H_
diff --git a/extensions/common/extension.h b/extensions/common/extension.h
index 97c0ca6eb..b87cc5f2dd 100644
--- a/extensions/common/extension.h
+++ b/extensions/common/extension.h
@@ -542,6 +542,7 @@
enum Reason {
ADDED, // The permissions were added to the extension.
REMOVED, // The permissions were removed from the extension.
+ POLICY, // The policy that affects permissions was updated.
};
Reason reason;
diff --git a/extensions/common/extension_messages.cc b/extensions/common/extension_messages.cc
index 8e56f09..c7d25b7 100644
--- a/extensions/common/extension_messages.cc
+++ b/extensions/common/extension_messages.cc
@@ -64,8 +64,14 @@
location(extension->location()),
path(extension->path()),
active_permissions(extension->permissions_data()->active_permissions()),
- withheld_permissions(extension->permissions_data()
- ->withheld_permissions()),
+ withheld_permissions(
+ extension->permissions_data()->withheld_permissions()),
+ policy_blocked_hosts(
+ extension->permissions_data()->policy_blocked_hosts()),
+ policy_allowed_hosts(
+ extension->permissions_data()->policy_allowed_hosts()),
+ uses_default_policy_blocked_allowed_hosts(
+ extension->permissions_data()->UsesDefaultPolicyHostRestrictions()),
id(extension->id()),
creation_flags(extension->creation_flags()) {
if (include_tab_permissions) {
@@ -92,6 +98,12 @@
extension->permissions_data();
permissions_data->SetPermissions(active_permissions.ToPermissionSet(),
withheld_permissions.ToPermissionSet());
+ if (uses_default_policy_blocked_allowed_hosts) {
+ permissions_data->SetUsesDefaultHostRestrictions();
+ } else {
+ permissions_data->SetPolicyHostRestrictions(policy_blocked_hosts,
+ policy_allowed_hosts);
+ }
for (const auto& pair : tab_specific_permissions) {
permissions_data->UpdateTabSpecificPermissions(
pair.first, *pair.second.ToPermissionSet());
@@ -359,6 +371,9 @@
WriteParam(m, p.active_permissions);
WriteParam(m, p.withheld_permissions);
WriteParam(m, p.tab_specific_permissions);
+ WriteParam(m, p.policy_blocked_hosts);
+ WriteParam(m, p.policy_allowed_hosts);
+ WriteParam(m, p.uses_default_policy_blocked_allowed_hosts);
}
bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const base::Pickle* m,
@@ -370,7 +385,10 @@
ReadParam(m, iter, &p->creation_flags) && ReadParam(m, iter, &p->id) &&
ReadParam(m, iter, &p->active_permissions) &&
ReadParam(m, iter, &p->withheld_permissions) &&
- ReadParam(m, iter, &p->tab_specific_permissions);
+ ReadParam(m, iter, &p->tab_specific_permissions) &&
+ ReadParam(m, iter, &p->policy_blocked_hosts) &&
+ ReadParam(m, iter, &p->policy_allowed_hosts) &&
+ ReadParam(m, iter, &p->uses_default_policy_blocked_allowed_hosts);
}
void ParamTraits<ExtensionMsg_Loaded_Params>::Log(const param_type& p,
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index f7952e8..456bff1 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -321,6 +321,14 @@
ExtensionMsg_PermissionSetStruct withheld_permissions;
std::map<int, ExtensionMsg_PermissionSetStruct> tab_specific_permissions;
+ // Contains URLPatternSets defining which URLs an extension may not interact
+ // with by policy.
+ extensions::URLPatternSet policy_blocked_hosts;
+ extensions::URLPatternSet policy_allowed_hosts;
+
+ // If the extension uses the default list of blocked / allowed URLs.
+ bool uses_default_policy_blocked_allowed_hosts = true;
+
// We keep this separate so that it can be used in logging.
std::string id;
@@ -442,6 +450,15 @@
IPC_STRUCT_MEMBER(std::string, extension_id)
IPC_STRUCT_MEMBER(ExtensionMsg_PermissionSetStruct, active_permissions)
IPC_STRUCT_MEMBER(ExtensionMsg_PermissionSetStruct, withheld_permissions)
+ IPC_STRUCT_MEMBER(extensions::URLPatternSet, policy_blocked_hosts)
+ IPC_STRUCT_MEMBER(extensions::URLPatternSet, policy_allowed_hosts)
+ IPC_STRUCT_MEMBER(bool, uses_default_policy_host_restrictions)
+IPC_STRUCT_END()
+
+// Parameters structure for ExtensionMsg_UpdateDefaultPolicyHostRestrictions.
+IPC_STRUCT_BEGIN(ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params)
+ IPC_STRUCT_MEMBER(extensions::URLPatternSet, default_policy_blocked_hosts)
+ IPC_STRUCT_MEMBER(extensions::URLPatternSet, default_policy_allowed_hosts)
IPC_STRUCT_END()
// Messages sent from the browser to the renderer:
@@ -548,6 +565,10 @@
IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdatePermissions,
ExtensionMsg_UpdatePermissions_Params)
+// Tell the renderer to update an extension's policy_blocked_hosts set.
+IPC_MESSAGE_CONTROL1(ExtensionMsg_UpdateDefaultPolicyHostRestrictions,
+ ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params)
+
// Tell the render view about new tab-specific permissions for an extension.
IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdateTabSpecificPermissions,
GURL /* url */,
diff --git a/extensions/common/permissions/permissions_data.cc b/extensions/common/permissions/permissions_data.cc
index 4025d23c..fb9cecb 100644
--- a/extensions/common/permissions/permissions_data.cc
+++ b/extensions/common/permissions/permissions_data.cc
@@ -8,6 +8,7 @@
#include <utility>
#include "base/command_line.h"
+#include "base/lazy_instance.h"
#include "base/macros.h"
#include "content/public/common/url_constants.h"
#include "extensions/common/constants.h"
@@ -30,6 +31,17 @@
PermissionsData::PolicyDelegate* g_policy_delegate = nullptr;
+struct DefaultRuntimePolicy {
+ URLPatternSet blocked_hosts;
+ URLPatternSet allowed_hosts;
+};
+
+// URLs an extension can't interact with. An extension can override these
+// settings by declaring its own list of blocked and allowed hosts using
+// policy_blocked_hosts and policy_allowed_hosts.
+base::LazyInstance<DefaultRuntimePolicy>::Leaky default_runtime_policy =
+ LAZY_INSTANCE_INITIALIZER;
+
class AutoLockOnValidThread {
public:
AutoLockOnValidThread(base::Lock& lock, base::ThreadChecker* thread_checker)
@@ -127,6 +139,45 @@
return false;
}
+bool PermissionsData::UsesDefaultPolicyHostRestrictions() const {
+ DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+ return uses_default_policy_host_restrictions;
+}
+
+const URLPatternSet& PermissionsData::default_policy_blocked_hosts() {
+ return default_runtime_policy.Get().blocked_hosts;
+}
+
+const URLPatternSet& PermissionsData::default_policy_allowed_hosts() {
+ return default_runtime_policy.Get().allowed_hosts;
+}
+
+const URLPatternSet PermissionsData::policy_blocked_hosts() const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return PolicyBlockedHostsUnsafe();
+}
+
+const URLPatternSet& PermissionsData::PolicyBlockedHostsUnsafe() const {
+ DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+ if (uses_default_policy_host_restrictions)
+ return default_policy_blocked_hosts();
+ runtime_lock_.AssertAcquired();
+ return policy_blocked_hosts_unsafe_;
+}
+
+const URLPatternSet PermissionsData::policy_allowed_hosts() const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return PolicyAllowedHostsUnsafe();
+}
+
+const URLPatternSet& PermissionsData::PolicyAllowedHostsUnsafe() const {
+ DCHECK(!thread_checker_ || thread_checker_->CalledOnValidThread());
+ if (uses_default_policy_host_restrictions)
+ return default_policy_allowed_hosts();
+ runtime_lock_.AssertAcquired();
+ return policy_allowed_hosts_unsafe_;
+}
+
void PermissionsData::BindToCurrentThread() const {
DCHECK(!thread_checker_);
thread_checker_.reset(new base::ThreadChecker());
@@ -140,6 +191,28 @@
withheld_permissions_unsafe_ = std::move(withheld);
}
+void PermissionsData::SetPolicyHostRestrictions(
+ const URLPatternSet& runtime_blocked_hosts,
+ const URLPatternSet& runtime_allowed_hosts) const {
+ AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+ policy_blocked_hosts_unsafe_ = runtime_blocked_hosts;
+ policy_allowed_hosts_unsafe_ = runtime_allowed_hosts;
+ uses_default_policy_host_restrictions = false;
+}
+
+void PermissionsData::SetUsesDefaultHostRestrictions() const {
+ AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
+ uses_default_policy_host_restrictions = true;
+}
+
+// static
+void PermissionsData::SetDefaultPolicyHostRestrictions(
+ const URLPatternSet& default_runtime_blocked_hosts,
+ const URLPatternSet& default_runtime_allowed_hosts) {
+ default_runtime_policy.Get().blocked_hosts = default_runtime_blocked_hosts;
+ default_runtime_policy.Get().allowed_hosts = default_runtime_allowed_hosts;
+}
+
void PermissionsData::SetActivePermissions(
std::unique_ptr<const PermissionSet> active) const {
AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
@@ -208,7 +281,8 @@
bool PermissionsData::HasHostPermission(const GURL& url) const {
base::AutoLock auto_lock(runtime_lock_);
- return active_permissions_unsafe_->HasExplicitAccessToOrigin(url);
+ return active_permissions_unsafe_->HasExplicitAccessToOrigin(url) &&
+ !IsRuntimeBlockedHost(url);
}
bool PermissionsData::HasEffectiveAccessToAllHosts() const {
@@ -327,6 +401,12 @@
return false;
}
+bool PermissionsData::IsRuntimeBlockedHost(const GURL& url) const {
+ runtime_lock_.AssertAcquired();
+ return PolicyBlockedHostsUnsafe().MatchesURL(url) &&
+ !PolicyAllowedHostsUnsafe().MatchesURL(url);
+}
+
PermissionsData::AccessType PermissionsData::CanRunOnPage(
const Extension* extension,
const GURL& document_url,
@@ -335,9 +415,14 @@
const URLPatternSet& withheld_url_patterns,
std::string* error) const {
runtime_lock_.AssertAcquired();
- if (g_policy_delegate &&
- !g_policy_delegate->CanExecuteScriptOnPage(extension, document_url,
- tab_id, error)) {
+ if (g_policy_delegate && !g_policy_delegate->CanExecuteScriptOnPage(
+ extension, document_url, tab_id, error))
+ return ACCESS_DENIED;
+
+ if (extension->location() != Manifest::COMPONENT &&
+ extension->permissions_data()->IsRuntimeBlockedHost(document_url)) {
+ if (error)
+ *error = extension_misc::kPolicyBlockedScripting;
return ACCESS_DENIED;
}
diff --git a/extensions/common/permissions/permissions_data.h b/extensions/common/permissions/permissions_data.h
index 3b87e79..6997403 100644
--- a/extensions/common/permissions/permissions_data.h
+++ b/extensions/common/permissions/permissions_data.h
@@ -82,6 +82,10 @@
const Extension* extension,
std::string* error);
+ // Is this extension using the default scope for policy_blocked_hosts and
+ // policy_allowed_hosts of the ExtensionSettings policy.
+ bool UsesDefaultPolicyHostRestrictions() const;
+
// Locks the permissions data to the current thread. We don't do this on
// construction, since extensions are initialized across multiple threads.
void BindToCurrentThread() const;
@@ -91,6 +95,27 @@
void SetPermissions(std::unique_ptr<const PermissionSet> active,
std::unique_ptr<const PermissionSet> withheld) const;
+ // Applies restrictions from enterprise policy limiting which URLs this
+ // extension can interact with. The same policy can also define a default set
+ // of URL restrictions using SetDefaultPolicyHostRestrictions. This function
+ // overrides any default host restriction policy.
+ void SetPolicyHostRestrictions(
+ const URLPatternSet& runtime_blocked_hosts,
+ const URLPatternSet& runtime_allowed_hosts) const;
+
+ // Marks this extension as using default enterprise policy limiting
+ // which URLs extensions can interact with. A default policy can be set with
+ // SetDefaultPolicyHostRestrictions. A policy specific to this extension
+ // can be set with SetPolicyHostRestrictions.
+ void SetUsesDefaultHostRestrictions() const;
+
+ // Applies restrictions from enterprise policy limiting which URLs all
+ // extensions can interact with. This restriction can be overridden on a
+ // per-extension basis with SetPolicyHostRestrictions.
+ static void SetDefaultPolicyHostRestrictions(
+ const URLPatternSet& default_runtime_blocked_hosts,
+ const URLPatternSet& default_runtime_allowed_hosts);
+
// Sets the active permissions, leaving withheld the same.
void SetActivePermissions(std::unique_ptr<const PermissionSet> active) const;
@@ -201,11 +226,42 @@
return *withheld_permissions_unsafe_;
}
+ // Returns list of hosts this extension may not interact with by policy.
+ // This should only be used for 1. Serialization when initializing renderers
+ // or 2. Called from utility methods above. For all other uses, call utility
+ // methods instead (e.g. CanAccessPage()).
+ static const URLPatternSet& default_policy_blocked_hosts();
+
+ // Returns list of hosts this extension may interact with regardless of
+ // what is defined by policy_blocked_hosts().
+ // This should only be used for 1. Serialization when initializing renderers
+ // or 2. Called from utility methods above. For all other uses, call utility
+ // methods instead (e.g. CanAccessPage()).
+ static const URLPatternSet& default_policy_allowed_hosts();
+
+ // Returns list of hosts this extension may not interact with by policy.
+ // This should only be used for 1. Serialization when initializing renderers
+ // or 2. Called from utility methods above. For all other uses, call utility
+ // methods instead (e.g. CanAccessPage()).
+ const URLPatternSet policy_blocked_hosts() const;
+
+ // Returns list of hosts this extension may interact with regardless of
+ // what is defined by policy_blocked_hosts().
+ // This should only be used for 1. Serialization when initializing renderers
+ // or 2. Called from utility methods above. For all other uses, call utility
+ // methods instead (e.g. CanAccessPage()).
+ const URLPatternSet policy_allowed_hosts() const;
+
#if defined(UNIT_TEST)
const PermissionSet* GetTabSpecificPermissionsForTesting(int tab_id) const {
base::AutoLock auto_lock(runtime_lock_);
return GetTabSpecificPermissions(tab_id);
}
+
+ bool IsRuntimeBlockedHostForTesting(const GURL& url) const {
+ base::AutoLock auto_lock(runtime_lock_);
+ return IsRuntimeBlockedHost(url);
+ }
#endif
private:
@@ -233,6 +289,17 @@
const URLPatternSet& withheld_url_patterns,
std::string* error) const;
+ // Check if a specific URL is blocked by policy from extension use at runtime.
+ bool IsRuntimeBlockedHost(const GURL& url) const;
+
+ // Same as policy_blocked_hosts but instead returns a reference.
+ // You must acquire runtime_lock_ before calling this.
+ const URLPatternSet& PolicyBlockedHostsUnsafe() const;
+
+ // Same as policy_allowed_hosts but instead returns a reference.
+ // You must acquire runtime_lock_ before calling this.
+ const URLPatternSet& PolicyAllowedHostsUnsafe() const;
+
// The associated extension's id.
std::string extension_id_;
@@ -255,6 +322,20 @@
// withheld_permissions() accessor.
mutable std::unique_ptr<const PermissionSet> withheld_permissions_unsafe_;
+ // The list of hosts an extension may not interact with by policy.
+ // Unless you need to change |policy_blocked_hosts_unsafe_|, use the (safe)
+ // policy_blocked_hosts() accessor.
+ mutable URLPatternSet policy_blocked_hosts_unsafe_;
+
+ // The exclusive list of hosts an extension may interact with by policy.
+ // Unless you need to change |policy_allowed_hosts_unsafe_|, use the (safe)
+ // policy_allowed_hosts() accessor.
+ mutable URLPatternSet policy_allowed_hosts_unsafe_;
+
+ // If the ExtensionSettings policy is not being used, or no per-extension
+ // exception to the default policy was declared for this extension.
+ mutable bool uses_default_policy_host_restrictions = true;
+
mutable TabPermissionsMap tab_specific_permissions_;
mutable std::unique_ptr<base::ThreadChecker> thread_checker_;
diff --git a/extensions/common/url_pattern.cc b/extensions/common/url_pattern.cc
index 5677fbc83..dba84a5 100644
--- a/extensions/common/url_pattern.cc
+++ b/extensions/common/url_pattern.cc
@@ -141,12 +141,14 @@
: valid_schemes_(SCHEME_NONE),
match_all_urls_(false),
match_subdomains_(false),
+ match_effective_tld_(true),
port_("*") {}
URLPattern::URLPattern(int valid_schemes)
: valid_schemes_(valid_schemes),
match_all_urls_(false),
match_subdomains_(false),
+ match_effective_tld_(true),
port_("*") {}
URLPattern::URLPattern(int valid_schemes, base::StringPiece pattern)
@@ -155,6 +157,7 @@
: valid_schemes_(valid_schemes),
match_all_urls_(false),
match_subdomains_(false),
+ match_effective_tld_(true),
port_("*") {
ParseResult result = Parse(pattern);
if (PARSE_SUCCESS != result)
@@ -183,9 +186,15 @@
}
URLPattern::ParseResult URLPattern::Parse(base::StringPiece pattern) {
+ return Parse(pattern, DENY_WILDCARD_FOR_EFFECTIVE_TLD);
+}
+
+URLPattern::ParseResult URLPattern::Parse(base::StringPiece pattern,
+ ParseOptions parse_options) {
spec_.clear();
SetMatchAllURLs(false);
SetMatchSubdomains(false);
+ SetMatchEffectiveTld(true);
SetPort("*");
// Special case pattern to match every valid URL.
@@ -267,6 +276,14 @@
host_components.erase(host_components.begin(),
host_components.begin() + 1);
}
+
+ // If explicitly allowed, the last component can optionally be '*' to
+ // match all effective TLDs.
+ if (parse_options == ALLOW_WILDCARD_FOR_EFFECTIVE_TLD &&
+ host_components.size() > 1 && host_components.back() == "*") {
+ match_effective_tld_ = false;
+ host_components.pop_back();
+ }
host_ = base::JoinString(host_components, ".");
path_start_pos = host_end_pos;
@@ -321,6 +338,11 @@
match_subdomains_ = val;
}
+void URLPattern::SetMatchEffectiveTld(bool val) {
+ spec_.clear();
+ match_effective_tld_ = val;
+}
+
bool URLPattern::SetScheme(base::StringPiece scheme) {
spec_.clear();
scheme.CopyToString(&scheme_);
@@ -421,10 +443,19 @@
}
bool URLPattern::MatchesHost(const GURL& test) const {
- const base::StringPiece test_host(
- CanonicalizeHostForMatching(test.host_piece()));
+ base::StringPiece test_host(CanonicalizeHostForMatching(test.host_piece()));
const base::StringPiece pattern_host(CanonicalizeHostForMatching(host_));
+ // If we don't care about matching the effective TLD, remove it.
+ if (!match_effective_tld_) {
+ int reg_length = net::registry_controlled_domains::GetRegistryLength(
+ test, net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+ net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ if (reg_length > 0) {
+ test_host = test_host.substr(0, test_host.size() - reg_length - 1);
+ }
+ }
+
// If the hosts are exactly equal, we have a match.
if (test_host == pattern_host)
return true;
@@ -529,6 +560,12 @@
if (!host_.empty())
spec += host_;
+ if (!match_effective_tld_) {
+ if (!host_.empty())
+ spec += ".";
+ spec += "*";
+ }
+
if (port_ != "*") {
spec += ":";
spec += port_;
diff --git a/extensions/common/url_pattern.h b/extensions/common/url_pattern.h
index 23f06878..efb93fa 100644
--- a/extensions/common/url_pattern.h
+++ b/extensions/common/url_pattern.h
@@ -81,6 +81,12 @@
NUM_PARSE_RESULTS
};
+ // Types of URLPattern that Parse() considers valid.
+ enum ParseOptions {
+ DENY_WILDCARD_FOR_EFFECTIVE_TLD,
+ ALLOW_WILDCARD_FOR_EFFECTIVE_TLD,
+ };
+
// The <all_urls> string pattern.
static const char kAllUrlsPattern[];
@@ -107,8 +113,10 @@
// Initializes this instance by parsing the provided string. Returns
// URLPattern::PARSE_SUCCESS on success, or an error code otherwise. On
// failure, this instance will have some intermediate values and is in an
- // invalid state.
+ // invalid state. If you want to allow the match pattern to specify a wildcard
+ // for the effective TLD, specify in |parse_options|.
ParseResult Parse(base::StringPiece pattern_str);
+ ParseResult Parse(base::StringPiece pattern_str, ParseOptions parse_options);
// Gets the bitmask of valid schemes.
int valid_schemes() const { return valid_schemes_; }
@@ -123,6 +131,15 @@
bool match_subdomains() const { return match_subdomains_; }
void SetMatchSubdomains(bool val);
+ // Gets whether host() contains an effective TLD. If false, during
+ // a match, the URL you're comparing must have its TLD removed
+ // prior to comparison.
+ // e.g. For the match pattern https://google.com/*
+ // If this is true: host() would be google.com
+ // If this is false: host() would be google
+ bool match_effective_tld() const { return match_effective_tld_; }
+ void SetMatchEffectiveTld(bool val);
+
// Gets the path the pattern matches with the leading slash. This can have
// embedded asterisks which are interpreted using glob rules.
const std::string& path() const { return path_; }
@@ -247,6 +264,12 @@
// component of the pattern's host was "*".
bool match_subdomains_;
+ // Whether we should match the effective TLD of the host. This is true by
+ // default and only false if ParseOptions is ALLOW_WILDCARD_FOR_EFFECTIVE_TLD
+ // and is only applicable when the the pattern's host ends with ".*"
+ // (e.g. https://example.*/*).
+ bool match_effective_tld_;
+
// The port.
std::string port_;
diff --git a/extensions/common/url_pattern_unittest.cc b/extensions/common/url_pattern_unittest.cc
index 62419f6..bd60583 100644
--- a/extensions/common/url_pattern_unittest.cc
+++ b/extensions/common/url_pattern_unittest.cc
@@ -28,19 +28,19 @@
const char* pattern;
URLPattern::ParseResult expected_result;
} kInvalidPatterns[] = {
- { "http", URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR },
- { "http:", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
- { "http:/", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
- { "about://", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
- { "http://", URLPattern::PARSE_ERROR_EMPTY_HOST },
- { "http:///", URLPattern::PARSE_ERROR_EMPTY_HOST },
- { "http:// /", URLPattern::PARSE_ERROR_EMPTY_HOST },
- { "http://*foo/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
- { "http://foo.*.bar/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
- { "http://fo.*.ba:123/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD },
- { "http:/bar", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR },
- { "http://bar", URLPattern::PARSE_ERROR_EMPTY_PATH },
- };
+ {"http", URLPattern::PARSE_ERROR_MISSING_SCHEME_SEPARATOR},
+ {"http:", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+ {"http:/", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+ {"about://", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+ {"http://", URLPattern::PARSE_ERROR_EMPTY_HOST},
+ {"http:///", URLPattern::PARSE_ERROR_EMPTY_HOST},
+ {"http:// /", URLPattern::PARSE_ERROR_EMPTY_HOST},
+ {"http://*foo/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD},
+ {"http://foo.*.bar/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD},
+ {"http://fo.*.ba:123/baz", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD},
+ {"http:/bar", URLPattern::PARSE_ERROR_WRONG_SCHEME_SEPARATOR},
+ {"http://bar", URLPattern::PARSE_ERROR_EMPTY_PATH},
+ {"http://foo.*/bar", URLPattern::PARSE_ERROR_INVALID_HOST_WILDCARD}};
for (size_t i = 0; i < arraysize(kInvalidPatterns); ++i) {
URLPattern pattern(URLPattern::SCHEME_ALL);
@@ -65,30 +65,31 @@
URLPattern::ParseResult expected_result;
const char* expected_port;
} kTestPatterns[] = {
- { "http://foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
- { "http://foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" },
- { "http://*.foo:1234/", URLPattern::PARSE_SUCCESS, "1234" },
- { "http://*.foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234" },
- { "http://:1234/", URLPattern::PARSE_SUCCESS, "1234" },
- { "http://foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
- { "http://foo:*/", URLPattern::PARSE_SUCCESS, "*" },
- { "http://*.foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
- { "http://foo:com/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
- { "http://foo:123456/", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
- { "http://foo:80:80/monkey", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
- { "file://foo:1234/bar", URLPattern::PARSE_SUCCESS, "*" },
- { "chrome://foo:1234/bar", URLPattern::PARSE_ERROR_INVALID_PORT, "*" },
+ {"http://foo:1234/", URLPattern::PARSE_SUCCESS, "1234"},
+ {"http://foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234"},
+ {"http://*.foo:1234/", URLPattern::PARSE_SUCCESS, "1234"},
+ {"http://*.foo:1234/bar", URLPattern::PARSE_SUCCESS, "1234"},
+ {"http://:1234/", URLPattern::PARSE_SUCCESS, "1234"},
+ {"http://foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+ {"http://foo:*/", URLPattern::PARSE_SUCCESS, "*"},
+ {"http://*.foo:/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+ {"http://foo:com/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+ {"http://foo:123456/", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+ {"http://foo:80:80/monkey", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
+ {"file://foo:1234/bar", URLPattern::PARSE_SUCCESS, "*"},
+ {"chrome://foo:1234/bar", URLPattern::PARSE_ERROR_INVALID_PORT, "*"},
- // Port-like strings in the path should not trigger a warning.
- { "http://*/:1234", URLPattern::PARSE_SUCCESS, "*" },
- { "http://*.foo/bar:1234", URLPattern::PARSE_SUCCESS, "*" },
- { "http://foo/bar:1234/path", URLPattern::PARSE_SUCCESS, "*" },
- };
+ // Port-like strings in the path should not trigger a warning.
+ {"http://*/:1234", URLPattern::PARSE_SUCCESS, "*"},
+ {"http://*.foo/bar:1234", URLPattern::PARSE_SUCCESS, "*"},
+ {"http://foo/bar:1234/path", URLPattern::PARSE_SUCCESS, "*"},
+ {"http://*.foo.*/:1234", URLPattern::PARSE_SUCCESS, "*"}};
for (size_t i = 0; i < arraysize(kTestPatterns); ++i) {
URLPattern pattern(URLPattern::SCHEME_ALL);
EXPECT_EQ(kTestPatterns[i].expected_result,
- pattern.Parse(kTestPatterns[i].pattern))
+ pattern.Parse(kTestPatterns[i].pattern,
+ URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD))
<< "Got unexpected result for URL pattern: "
<< kTestPatterns[i].pattern;
EXPECT_EQ(kTestPatterns[i].expected_port, pattern.port())
@@ -103,6 +104,7 @@
EXPECT_EQ("http", pattern.scheme());
EXPECT_EQ("", pattern.host());
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com")));
@@ -119,6 +121,7 @@
EXPECT_EQ("https", pattern.scheme());
EXPECT_EQ("", pattern.host());
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/foo*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("https://www.google.com/foo")));
@@ -137,6 +140,7 @@
EXPECT_EQ("http", pattern.scheme());
EXPECT_EQ("google.com", pattern.host());
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/foo*bar", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar")));
@@ -157,6 +161,7 @@
EXPECT_EQ("file", pattern.scheme());
EXPECT_EQ("", pattern.host());
EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/foo?bar\\*baz", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("file:///foo?bar\\hellobaz")));
@@ -170,6 +175,7 @@
EXPECT_EQ("http", pattern.scheme());
EXPECT_EQ("127.0.0.1", pattern.host());
EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
@@ -183,6 +189,7 @@
EXPECT_EQ("http", pattern.scheme());
EXPECT_EQ("0.0.1", pattern.host());
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/*", pattern.path());
// Subdomain matching is never done if the argument has an IP address host.
@@ -199,6 +206,7 @@
EXPECT_EQ("http", pattern.scheme());
EXPECT_EQ("xn--gkd", pattern.host());
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/a%C2%81%E1*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(
@@ -214,6 +222,7 @@
EXPECT_EQ("chrome", pattern.scheme());
EXPECT_EQ("favicon", pattern.host());
EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
@@ -231,6 +240,7 @@
EXPECT_FALSE(pattern.MatchesScheme("file"));
EXPECT_FALSE(pattern.MatchesScheme("ftp"));
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("http://127.0.0.1")));
@@ -250,6 +260,7 @@
EXPECT_TRUE(pattern.MatchesScheme("filesystem"));
EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_TRUE(pattern.match_all_urls());
EXPECT_EQ("/*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
@@ -285,6 +296,7 @@
EXPECT_TRUE(pattern.MatchesScheme("about"));
EXPECT_TRUE(pattern.MatchesScheme("chrome-extension"));
EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_TRUE(pattern.match_all_urls());
EXPECT_EQ("/*", pattern.path());
EXPECT_TRUE(pattern.MatchesURL(GURL("chrome://favicon/http://google.com")));
@@ -336,6 +348,7 @@
EXPECT_EQ("", pattern.host());
EXPECT_FALSE(pattern.match_subdomains());
EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_EQ("/foo*", pattern.path());
EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
@@ -352,6 +365,7 @@
EXPECT_EQ("", pattern.host());
EXPECT_FALSE(pattern.match_subdomains());
EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_EQ("/foo*", pattern.path());
EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
EXPECT_FALSE(pattern.MatchesURL(GURL("file://foobar")));
@@ -368,6 +382,7 @@
// Since hostname is ignored for file://.
EXPECT_EQ("", pattern.host());
EXPECT_FALSE(pattern.match_subdomains());
+ EXPECT_TRUE(pattern.match_effective_tld());
EXPECT_FALSE(pattern.match_all_urls());
EXPECT_EQ("/foo*", pattern.path());
EXPECT_FALSE(pattern.MatchesURL(GURL("file://foo")));
@@ -435,6 +450,39 @@
GURL("filesystem:chrome-extension://ftw/t/file.txt")));
}
+// effective TLD wildcard
+TEST(URLPatternTest, EffectiveTldWildcard) {
+ URLPattern pattern(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern.Parse("http://*.google.*/foo*bar",
+ URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD));
+ EXPECT_EQ("http", pattern.scheme());
+ EXPECT_EQ("google", pattern.host());
+ EXPECT_TRUE(pattern.match_subdomains());
+ EXPECT_FALSE(pattern.match_effective_tld());
+ EXPECT_FALSE(pattern.match_all_urls());
+ EXPECT_EQ("/foo*bar", pattern.path());
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://google.com/foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("http://www.google.com.br/foo?bar")));
+ EXPECT_TRUE(
+ pattern.MatchesURL(GURL("http://monkey.images.google.co.uk/foooobar")));
+ EXPECT_FALSE(pattern.MatchesURL(GURL("http://yahoo.com/foobar")));
+ EXPECT_TRUE(pattern.MatchesURL(GURL("filesystem:http://google.com/foo/bar")));
+ EXPECT_FALSE(pattern.MatchesURL(
+ GURL("filesystem:http://google.com/temporary/foobar")));
+ URLPattern pattern_sub(kAllSchemes);
+ EXPECT_EQ(URLPattern::PARSE_SUCCESS,
+ pattern_sub.Parse("https://maps.google.*/",
+ URLPattern::ALLOW_WILDCARD_FOR_EFFECTIVE_TLD));
+ EXPECT_EQ("https", pattern_sub.scheme());
+ EXPECT_EQ("maps.google", pattern_sub.host());
+ EXPECT_FALSE(pattern_sub.match_subdomains());
+ EXPECT_FALSE(pattern_sub.match_all_urls());
+ EXPECT_EQ("/", pattern_sub.path());
+ EXPECT_TRUE(pattern_sub.MatchesURL(GURL("https://maps.google.co.uk/")));
+ EXPECT_FALSE(pattern_sub.MatchesURL(GURL("https://sub.maps.google.co.uk/")));
+}
+
static const struct GetAsStringPatterns {
const char* pattern;
} kGetAsStringTestCases[] = {
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index 86aa533..429056c 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -908,6 +908,8 @@
IPC_MESSAGE_HANDLER(ExtensionMsg_TransferBlobs, OnTransferBlobs)
IPC_MESSAGE_HANDLER(ExtensionMsg_Unloaded, OnUnloaded)
IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions)
+ IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateDefaultPolicyHostRestrictions,
+ OnUpdateDefaultPolicyHostRestrictions)
IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateTabSpecificPermissions,
OnUpdateTabSpecificPermissions)
IPC_MESSAGE_HANDLER(ExtensionMsg_ClearTabSpecificPermissions,
@@ -1031,7 +1033,6 @@
extension_load_errors_[param.id] = error;
continue;
}
-
RendererExtensionRegistry* extension_registry =
RendererExtensionRegistry::Get();
// TODO(kalman): This test is deliberately not a CHECK (though I wish it
@@ -1048,12 +1049,18 @@
// consider making this a release CHECK.
NOTREACHED();
}
+ if (param.uses_default_policy_blocked_allowed_hosts) {
+ extension->permissions_data()->SetUsesDefaultHostRestrictions();
+ } else {
+ extension->permissions_data()->SetPolicyHostRestrictions(
+ param.policy_blocked_hosts, param.policy_allowed_hosts);
+ }
}
// Update the available bindings for all contexts. These may have changed if
// an externally_connectable extension was loaded that can connect to an
// open webpage.
- UpdateBindings("");
+ UpdateBindings(std::string());
}
void Dispatcher::OnMessageInvoke(const std::string& extension_id,
@@ -1180,6 +1187,13 @@
// extension's URL just won't match anything anymore.
}
+void Dispatcher::OnUpdateDefaultPolicyHostRestrictions(
+ const ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params& params) {
+ PermissionsData::SetDefaultPolicyHostRestrictions(
+ params.default_policy_blocked_hosts, params.default_policy_allowed_hosts);
+ UpdateBindings(std::string());
+}
+
void Dispatcher::OnUpdatePermissions(
const ExtensionMsg_UpdatePermissions_Params& params) {
const Extension* extension =
@@ -1199,6 +1213,12 @@
extension->permissions_data()->SetPermissions(std::move(active),
std::move(withheld));
+ if (params.uses_default_policy_host_restrictions) {
+ extension->permissions_data()->SetUsesDefaultHostRestrictions();
+ } else {
+ extension->permissions_data()->SetPolicyHostRestrictions(
+ params.policy_blocked_hosts, params.policy_allowed_hosts);
+ }
UpdateBindings(extension->id());
}
diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h
index 29b7bb8..4ef3c004 100644
--- a/extensions/renderer/dispatcher.h
+++ b/extensions/renderer/dispatcher.h
@@ -42,6 +42,7 @@
struct ExtensionMsg_Loaded_Params;
struct ExtensionMsg_TabConnectionInfo;
struct ExtensionMsg_UpdatePermissions_Params;
+struct ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params;
namespace blink {
class WebLocalFrame;
@@ -183,6 +184,8 @@
void OnTransferBlobs(const std::vector<std::string>& blob_uuids);
void OnUnloaded(const std::string& id);
void OnUpdatePermissions(const ExtensionMsg_UpdatePermissions_Params& params);
+ void OnUpdateDefaultPolicyHostRestrictions(
+ const ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params& params);
void OnUpdateTabSpecificPermissions(const GURL& visible_url,
const std::string& extension_id,
const URLPatternSet& new_hosts,