blob: 08c15080f35072e9a700f0e7550d8d93d341ad0b [file] [log] [blame]
// 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 <utility>
#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/browser/extension_prefs.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<const Extension> CreateExtensionWithPermissions(
const std::set<URLPattern>& scriptable_hosts,
const std::set<URLPattern>& explicit_hosts,
Manifest::Location location,
const std::string& name) {
ListBuilder scriptable_host_list;
for (std::set<URLPattern>::const_iterator pattern = scriptable_hosts.begin();
pattern != scriptable_hosts.end(); ++pattern) {
scriptable_host_list.Append(pattern->GetAsString());
}
ListBuilder explicit_host_list;
for (std::set<URLPattern>::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<URLPattern>& set1,
const std::set<URLPattern>& 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<URLPattern> difference = base::STLSetUnion<std::set<URLPattern>>(
base::STLSetDifference<std::set<URLPattern>>(set1, set2),
base::STLSetDifference<std::set<URLPattern>>(set2, set1));
std::string error;
for (std::set<URLPattern>::const_iterator iter = difference.begin();
iter != difference.end(); ++iter) {
if (iter->GetAsString() == "chrome://favicon/*")
continue; // Grr... This is auto-added for extensions with <all_urls>
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.
std::unique_ptr<FeatureSwitch::ScopedOverride> 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, "<all_urls>");
URLPattern all_com(URLPattern::SCHEME_ALL, "http://*.com/*");
std::set<URLPattern> all_host_patterns;
std::set<URLPattern> 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<URLPattern> all_patterns =
base::STLSetUnion<std::set<URLPattern>>(all_host_patterns, safe_patterns);
scoped_refptr<const Extension> 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.SetAllowedOnAllUrls(true);
// 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.SetAllowedOnAllUrls(false);
// 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, "<all_urls>");
std::set<URLPattern> all_host_patterns;
all_host_patterns.insert(all_hosts);
scoped_refptr<const Extension> 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());
ScriptingPermissionsModifier modifier_a(profile(), extension_a);
EXPECT_TRUE(modifier_a.IsAllowedOnAllUrls());
// Enable the switch, and re-init permission for the extension.
std::unique_ptr<FeatureSwitch::ScopedOverride> 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(modifier_a.IsAllowedOnAllUrls());
// Load a new extension, which also has all urls. Since the switch is now on,
// the permissions should be withheld.
scoped_refptr<const Extension> 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));
ScriptingPermissionsModifier modifier_b(profile(), extension_b);
EXPECT_FALSE(modifier_b.IsAllowedOnAllUrls());
// 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(modifier_b.IsAllowedOnAllUrls());
}
TEST_F(ScriptingPermissionsModifierUnitTest, GrantHostPermission) {
InitializeEmptyExtensionService();
// Permissions are only withheld with the appropriate switch turned on.
std::unique_ptr<FeatureSwitch::ScopedOverride> switch_override(
new FeatureSwitch::ScopedOverride(FeatureSwitch::scripts_require_action(),
FeatureSwitch::OVERRIDE_ENABLED));
URLPattern all_hosts(URLPattern::SCHEME_ALL, "<all_urls>");
std::set<URLPattern> all_host_patterns;
all_host_patterns.insert(all_hosts);
scoped_refptr<const Extension> 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));
}
// Checks that policy-installed extensions don't have permissions withheld and
// that preferences are correctly recovered in the case of an improper value.
// Fix for crbug.com/629927.
TEST_F(ScriptingPermissionsModifierUnitTest,
PolicyExtensionsCanExecuteEverywhere) {
std::unique_ptr<FeatureSwitch::ScopedOverride> switch_override(
new FeatureSwitch::ScopedOverride(FeatureSwitch::scripts_require_action(),
FeatureSwitch::OVERRIDE_ENABLED));
InitializeEmptyExtensionService();
URLPattern all_hosts(URLPattern::SCHEME_ALL, "<all_urls>");
std::set<URLPattern> all_host_patterns;
all_host_patterns.insert(all_hosts);
scoped_refptr<const Extension> extension =
CreateExtensionWithPermissions(all_host_patterns, all_host_patterns,
Manifest::EXTERNAL_POLICY, "extension");
PermissionsUpdater(profile()).InitializePermissions(extension.get());
ScriptingPermissionsModifier modifier(profile(), extension);
EXPECT_TRUE(modifier.IsAllowedOnAllUrls());
// Simulate preferences being incorrectly set.
const char* kAllowedPref = "extension_can_script_all_urls";
const char* kHasSetPref = "has_set_script_all_urls";
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
prefs->UpdateExtensionPref(extension->id(), kAllowedPref,
std::make_unique<base::Value>(false));
prefs->UpdateExtensionPref(extension->id(), kHasSetPref,
std::make_unique<base::Value>(true));
// The modifier should still return the correct value and should fix the
// preferences.
EXPECT_TRUE(modifier.IsAllowedOnAllUrls());
bool stored_allowed = false;
EXPECT_TRUE(
prefs->ReadPrefAsBoolean(extension->id(), kAllowedPref, &stored_allowed));
EXPECT_TRUE(stored_allowed);
bool has_set = false;
EXPECT_FALSE(
prefs->ReadPrefAsBoolean(extension->id(), kHasSetPref, &has_set));
}
} // namespace extensions