// Copyright 2015 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 #include "base/strings/stringprintf.h" #include "chrome/browser/extensions/extension_service_test_base.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/permissions_updater.h" #include "chrome/browser/extensions/scripting_permissions_modifier.h" #include "chrome/test/base/testing_profile.h" #include "components/crx_file/id_util.h" #include "extensions/common/extension.h" #include "extensions/common/extension_builder.h" #include "extensions/common/feature_switch.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/url_pattern.h" #include "extensions/common/url_pattern_set.h" #include "extensions/common/value_builder.h" namespace extensions { namespace { scoped_refptr CreateExtensionWithPermissions( const std::set& scriptable_hosts, const std::set& explicit_hosts, Manifest::Location location, const std::string& name) { ListBuilder scriptable_host_list; for (std::set::const_iterator pattern = scriptable_hosts.begin(); pattern != scriptable_hosts.end(); ++pattern) { scriptable_host_list.Append(pattern->GetAsString()); } ListBuilder explicit_host_list; for (std::set::const_iterator pattern = explicit_hosts.begin(); pattern != explicit_hosts.end(); ++pattern) { explicit_host_list.Append(pattern->GetAsString()); } DictionaryBuilder script; script.Set("matches", scriptable_host_list.Build()) .Set("js", ListBuilder().Append("foo.js").Build()); return ExtensionBuilder() .SetLocation(location) .SetManifest(DictionaryBuilder() .Set("name", name) .Set("description", "foo") .Set("manifest_version", 2) .Set("version", "0.1.2.3") .Set("content_scripts", ListBuilder().Append(script.Build()).Build()) .Set("permissions", explicit_host_list.Build()) .Build()) .SetID(crx_file::id_util::GenerateId(name)) .Build(); } testing::AssertionResult SetsAreEqual(const std::set& set1, const std::set& set2) { // Take the (set1 - set2) U (set2 - set1). This is then the set of all // elements which are in either set1 or set2, but not both. // If the sets are equal, this is none. std::set difference = base::STLSetUnion>( base::STLSetDifference>(set1, set2), base::STLSetDifference>(set2, set1)); std::string error; for (std::set::const_iterator iter = difference.begin(); iter != difference.end(); ++iter) { if (iter->GetAsString() == "chrome://favicon/*") continue; // Grr... This is auto-added for extensions with error = base::StringPrintf( "%s\n%s contains %s and the other does not.", error.c_str(), (set1.count(*iter) ? "Set1" : "Set2"), iter->GetAsString().c_str()); } if (!error.empty()) return testing::AssertionFailure() << error; return testing::AssertionSuccess(); } using ScriptingPermissionsModifierUnitTest = ExtensionServiceTestBase; } // namespace TEST_F(ScriptingPermissionsModifierUnitTest, WithholdAllHosts) { InitializeEmptyExtensionService(); // Permissions are only withheld with the appropriate switch turned on. scoped_ptr switch_override( new FeatureSwitch::ScopedOverride(FeatureSwitch::scripts_require_action(), FeatureSwitch::OVERRIDE_ENABLED)); URLPattern google(URLPattern::SCHEME_ALL, "http://www.google.com/*"); URLPattern sub_google(URLPattern::SCHEME_ALL, "http://*.google.com/*"); URLPattern all_http(URLPattern::SCHEME_ALL, "http://*/*"); URLPattern all_hosts(URLPattern::SCHEME_ALL, ""); URLPattern all_com(URLPattern::SCHEME_ALL, "http://*.com/*"); std::set all_host_patterns; std::set safe_patterns; all_host_patterns.insert(all_http); all_host_patterns.insert(all_hosts); all_host_patterns.insert(all_com); safe_patterns.insert(google); safe_patterns.insert(sub_google); std::set all_patterns = base::STLSetUnion>(all_host_patterns, safe_patterns); scoped_refptr extension = CreateExtensionWithPermissions( all_patterns, all_patterns, Manifest::INTERNAL, "a"); const PermissionsData* permissions_data = extension->permissions_data(); PermissionsUpdater updater(profile()); updater.InitializePermissions(extension.get()); // At first, the active permissions should have only the safe patterns and // the withheld permissions should have only the all host patterns. EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().scriptable_hosts().patterns(), safe_patterns)); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().explicit_hosts().patterns(), safe_patterns)); EXPECT_TRUE(SetsAreEqual( permissions_data->withheld_permissions().scriptable_hosts().patterns(), all_host_patterns)); EXPECT_TRUE(SetsAreEqual( permissions_data->withheld_permissions().explicit_hosts().patterns(), all_host_patterns)); ScriptingPermissionsModifier modifier(profile(), extension); // Then, we grant the withheld all-hosts permissions. modifier.GrantWithheldImpliedAllHosts(); // Now, active permissions should have all patterns, and withheld permissions // should have none. EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().scriptable_hosts().patterns(), all_patterns)); EXPECT_TRUE(permissions_data->withheld_permissions() .scriptable_hosts() .patterns() .empty()); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().explicit_hosts().patterns(), all_patterns)); EXPECT_TRUE(permissions_data->withheld_permissions() .explicit_hosts() .patterns() .empty()); // Finally, we revoke the all hosts permissions. modifier.WithholdImpliedAllHosts(); // We should be back to our initial state - all_hosts should be withheld, and // the safe patterns should be granted. EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().scriptable_hosts().patterns(), safe_patterns)); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().explicit_hosts().patterns(), safe_patterns)); EXPECT_TRUE(SetsAreEqual( permissions_data->withheld_permissions().scriptable_hosts().patterns(), all_host_patterns)); EXPECT_TRUE(SetsAreEqual( permissions_data->withheld_permissions().explicit_hosts().patterns(), all_host_patterns)); // Creating a component extension should result in no withheld permissions. extension = CreateExtensionWithPermissions(all_patterns, all_patterns, Manifest::COMPONENT, "b"); permissions_data = extension->permissions_data(); updater.InitializePermissions(extension.get()); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().scriptable_hosts().patterns(), all_patterns)); EXPECT_TRUE(permissions_data->withheld_permissions() .scriptable_hosts() .patterns() .empty()); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().explicit_hosts().patterns(), all_patterns)); EXPECT_TRUE(permissions_data->withheld_permissions() .explicit_hosts() .patterns() .empty()); // Without the switch, we shouldn't withhold anything. switch_override.reset(); extension = CreateExtensionWithPermissions(all_patterns, all_patterns, Manifest::INTERNAL, "c"); permissions_data = extension->permissions_data(); updater.InitializePermissions(extension.get()); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().scriptable_hosts().patterns(), all_patterns)); EXPECT_TRUE(permissions_data->withheld_permissions() .scriptable_hosts() .patterns() .empty()); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().explicit_hosts().patterns(), all_patterns)); EXPECT_TRUE(permissions_data->withheld_permissions() .explicit_hosts() .patterns() .empty()); } // Tests that withholding all hosts behaves properly with extensions installed // when the switch is turned on and off. TEST_F(ScriptingPermissionsModifierUnitTest, WithholdAllHostsWithTransientSwitch) { InitializeEmptyExtensionService(); URLPattern all_hosts(URLPattern::SCHEME_ALL, ""); std::set all_host_patterns; all_host_patterns.insert(all_hosts); scoped_refptr extension_a = CreateExtensionWithPermissions( all_host_patterns, all_host_patterns, Manifest::INTERNAL, "a"); PermissionsUpdater updater(profile()); updater.InitializePermissions(extension_a.get()); const PermissionsData* permissions_data = extension_a->permissions_data(); // Since the extension was created without the switch on, it should default // to having all urls access. EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().scriptable_hosts().patterns(), all_host_patterns)); EXPECT_TRUE( permissions_data->withheld_permissions().scriptable_hosts().is_empty()); EXPECT_TRUE(util::AllowedScriptingOnAllUrls(extension_a->id(), profile())); // Enable the switch, and re-init permission for the extension. scoped_ptr switch_override( new FeatureSwitch::ScopedOverride(FeatureSwitch::scripts_require_action(), FeatureSwitch::OVERRIDE_ENABLED)); updater.InitializePermissions(extension_a.get()); // Since the extension was installed when the switch was off, it should still // have the all urls pref. permissions_data = extension_a->permissions_data(); EXPECT_TRUE(SetsAreEqual( permissions_data->active_permissions().scriptable_hosts().patterns(), all_host_patterns)); EXPECT_TRUE( permissions_data->withheld_permissions().scriptable_hosts().is_empty()); EXPECT_TRUE(util::AllowedScriptingOnAllUrls(extension_a->id(), profile())); // Load a new extension, which also has all urls. Since the switch is now on, // the permissions should be withheld. scoped_refptr extension_b = CreateExtensionWithPermissions( all_host_patterns, all_host_patterns, Manifest::INTERNAL, "b"); updater.InitializePermissions(extension_b.get()); permissions_data = extension_b->permissions_data(); EXPECT_TRUE( permissions_data->active_permissions().scriptable_hosts().is_empty()); EXPECT_TRUE(SetsAreEqual( permissions_data->withheld_permissions().scriptable_hosts().patterns(), all_host_patterns)); EXPECT_FALSE(util::AllowedScriptingOnAllUrls(extension_b->id(), profile())); // Disable the switch, and reload the extension. switch_override.reset(); updater.InitializePermissions(extension_b.get()); // Since the extension was installed with the switch on, it should still be // restricted with the switch off. permissions_data = extension_b->permissions_data(); EXPECT_TRUE( permissions_data->active_permissions().scriptable_hosts().is_empty()); EXPECT_TRUE(SetsAreEqual( permissions_data->withheld_permissions().scriptable_hosts().patterns(), all_host_patterns)); EXPECT_FALSE(util::AllowedScriptingOnAllUrls(extension_b->id(), profile())); } TEST_F(ScriptingPermissionsModifierUnitTest, GrantHostPermission) { InitializeEmptyExtensionService(); // Permissions are only withheld with the appropriate switch turned on. scoped_ptr switch_override( new FeatureSwitch::ScopedOverride(FeatureSwitch::scripts_require_action(), FeatureSwitch::OVERRIDE_ENABLED)); URLPattern all_hosts(URLPattern::SCHEME_ALL, ""); std::set all_host_patterns; all_host_patterns.insert(all_hosts); scoped_refptr extension = CreateExtensionWithPermissions( all_host_patterns, all_host_patterns, Manifest::INTERNAL, "extension"); PermissionsUpdater(profile()).InitializePermissions(extension.get()); ScriptingPermissionsModifier modifier(profile(), extension); const GURL kUrl("https://www.google.com/"); const GURL kUrl2("https://www.chromium.org/"); EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl)); EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl2)); const PermissionsData* permissions = extension->permissions_data(); auto get_page_access = [&permissions, &extension](const GURL& url) { return permissions->GetPageAccess(extension.get(), url, 0, nullptr); }; EXPECT_EQ(PermissionsData::ACCESS_WITHHELD, get_page_access(kUrl)); EXPECT_EQ(PermissionsData::ACCESS_WITHHELD, get_page_access(kUrl2)); modifier.GrantHostPermission(kUrl); EXPECT_TRUE(modifier.HasGrantedHostPermission(kUrl)); EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl2)); EXPECT_EQ(PermissionsData::ACCESS_ALLOWED, get_page_access(kUrl)); EXPECT_EQ(PermissionsData::ACCESS_WITHHELD, get_page_access(kUrl2)); modifier.RemoveGrantedHostPermission(kUrl); EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl)); EXPECT_FALSE(modifier.HasGrantedHostPermission(kUrl2)); EXPECT_EQ(PermissionsData::ACCESS_WITHHELD, get_page_access(kUrl)); EXPECT_EQ(PermissionsData::ACCESS_WITHHELD, get_page_access(kUrl2)); } } // namespace extensions