| // 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 "chrome/browser/extensions/scripting_permissions_modifier.h" |
| |
| #include "base/memory/ptr_util.h" |
| #include "chrome/browser/extensions/extension_sync_service.h" |
| #include "chrome/browser/extensions/permissions_updater.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/feature_switch.h" |
| #include "extensions/common/manifest_handlers/permissions_parser.h" |
| #include "extensions/common/permissions/permission_set.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "extensions/common/user_script.h" |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // The entry into the ExtensionPrefs for allowing an extension to script on |
| // all urls without explicit permission. |
| const char kExtensionAllowedOnAllUrlsPrefName[] = |
| "extension_can_script_all_urls"; |
| |
| // The entry into the prefs for when a user has explicitly set the "extension |
| // allowed on all urls" pref. |
| const char kHasSetScriptOnAllUrlsPrefName[] = "has_set_script_all_urls"; |
| |
| URLPatternSet FilterImpliedAllHostsPatterns(const URLPatternSet& patterns) { |
| URLPatternSet result; |
| for (const URLPattern& pattern : patterns) { |
| if (pattern.ImpliesAllHosts()) |
| result.AddPattern(pattern); |
| } |
| return result; |
| } |
| |
| // Returns true if the extension must be allowed to execute scripts on all urls. |
| bool ExtensionMustBeAllowedOnAllUrls(const Extension& extension) { |
| // Some extensions must retain privilege to execute on all urls. Specifically, |
| // extensions that don't show up in chrome:extensions (where withheld |
| // permissions couldn't be granted), extensions that are part of chrome or |
| // corporate policy, and extensions that are whitelisted to script everywhere |
| // must always have permission to run on a page. |
| return !extension.ShouldDisplayInExtensionSettings() || |
| Manifest::IsPolicyLocation(extension.location()) || |
| Manifest::IsComponentLocation(extension.location()) || |
| PermissionsData::CanExecuteScriptEverywhere(&extension); |
| } |
| |
| // Sets the preference for whether the extension with |id| is allowed to execute |
| // on all urls, and, if |by_user| is true, also updates preferences to indicate |
| // that the user has explicitly set a value (rather than using the default). |
| void SetAllowedOnAllUrlsPref(bool by_user, |
| bool allowed, |
| const std::string& id, |
| ExtensionPrefs* prefs) { |
| prefs->UpdateExtensionPref(id, kExtensionAllowedOnAllUrlsPrefName, |
| base::MakeUnique<base::Value>(allowed)); |
| if (by_user) { |
| prefs->UpdateExtensionPref(id, kHasSetScriptOnAllUrlsPrefName, |
| base::MakeUnique<base::Value>(true)); |
| } |
| } |
| |
| } // namespace |
| |
| ScriptingPermissionsModifier::ScriptingPermissionsModifier( |
| content::BrowserContext* browser_context, |
| const scoped_refptr<const Extension>& extension) |
| : browser_context_(browser_context), |
| extension_(extension), |
| extension_prefs_(ExtensionPrefs::Get(browser_context_)) { |
| DCHECK(extension_); |
| } |
| |
| ScriptingPermissionsModifier::~ScriptingPermissionsModifier() {} |
| |
| // static |
| void ScriptingPermissionsModifier::SetAllowedOnAllUrlsForSync( |
| bool allowed, |
| content::BrowserContext* context, |
| const std::string& id) { |
| const Extension* extension = |
| ExtensionRegistry::Get(context)->GetExtensionById( |
| id, ExtensionRegistry::EVERYTHING); |
| if (extension) { |
| // If the extension exists, we should go through the normal flow. |
| ScriptingPermissionsModifier(context, extension) |
| .SetAllowedOnAllUrls(allowed); |
| return; |
| } |
| // Otherwise, we only update the preference, and the extension will be |
| // properly initialized once it's added. |
| SetAllowedOnAllUrlsPref(true, allowed, id, ExtensionPrefs::Get(context)); |
| } |
| |
| // static |
| bool ScriptingPermissionsModifier::DefaultAllowedOnAllUrls() { |
| return !FeatureSwitch::scripts_require_action()->IsEnabled(); |
| } |
| |
| void ScriptingPermissionsModifier::SetAllowedOnAllUrls(bool allowed) { |
| if (ExtensionMustBeAllowedOnAllUrls(*extension_)) { |
| CleanUpPrefsIfNecessary(); |
| return; |
| } |
| if (IsAllowedOnAllUrls() == allowed) |
| return; |
| |
| SetAllowedOnAllUrlsPref(true, allowed, extension_->id(), extension_prefs_); |
| if (allowed) |
| GrantWithheldImpliedAllHosts(); |
| else |
| WithholdImpliedAllHosts(); |
| |
| // If this was an update to permissions, we also need to sync the change. |
| ExtensionSyncService* sync_service = |
| ExtensionSyncService::Get(browser_context_); |
| if (sync_service) // |sync_service| can be null in unittests. |
| sync_service->SyncExtensionChangeIfNeeded(*extension_); |
| } |
| |
| bool ScriptingPermissionsModifier::IsAllowedOnAllUrls() { |
| if (ExtensionMustBeAllowedOnAllUrls(*extension_)) { |
| CleanUpPrefsIfNecessary(); |
| return true; |
| } |
| bool allowed = false; |
| if (!extension_prefs_->ReadPrefAsBoolean( |
| extension_->id(), kExtensionAllowedOnAllUrlsPrefName, &allowed)) { |
| // If there is no value present, we make one, defaulting it to the value of |
| // the 'scripts require action' flag. If the flag is on, then the extension |
| // does not have permission to script on all urls by default. |
| allowed = DefaultAllowedOnAllUrls(); |
| SetAllowedOnAllUrlsPref(false, allowed, extension_->id(), extension_prefs_); |
| } |
| return allowed; |
| } |
| |
| bool ScriptingPermissionsModifier::HasSetAllowedOnAllUrls() const { |
| bool set = false; |
| return extension_prefs_->ReadPrefAsBoolean( |
| extension_->id(), kHasSetScriptOnAllUrlsPrefName, &set) && |
| set; |
| } |
| |
| bool ScriptingPermissionsModifier::CanAffectExtension( |
| const PermissionSet& permissions) const { |
| // We can withhold permissions if the extension isn't required to maintain |
| // permission and if it requests access to all hosts. |
| return !ExtensionMustBeAllowedOnAllUrls(*extension_) && |
| permissions.ShouldWarnAllHosts(); |
| } |
| |
| bool ScriptingPermissionsModifier::HasAffectedExtension() const { |
| return extension_->permissions_data()->HasWithheldImpliedAllHosts() || |
| HasSetAllowedOnAllUrls(); |
| } |
| |
| void ScriptingPermissionsModifier::GrantHostPermission(const GURL& url) { |
| GURL origin = url.GetOrigin(); |
| URLPatternSet new_explicit_hosts; |
| URLPatternSet new_scriptable_hosts; |
| |
| const PermissionSet& withheld_permissions = |
| extension_->permissions_data()->withheld_permissions(); |
| if (withheld_permissions.explicit_hosts().MatchesURL(url)) { |
| new_explicit_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(), origin); |
| } |
| if (withheld_permissions.scriptable_hosts().MatchesURL(url)) { |
| new_scriptable_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(), |
| origin); |
| } |
| |
| PermissionsUpdater(browser_context_) |
| .AddPermissions(extension_.get(), |
| PermissionSet(APIPermissionSet(), ManifestPermissionSet(), |
| new_explicit_hosts, new_scriptable_hosts)); |
| } |
| |
| bool ScriptingPermissionsModifier::HasGrantedHostPermission(const GURL& url) { |
| GURL origin = url.GetOrigin(); |
| const PermissionSet& required_permissions = |
| PermissionsParser::GetRequiredPermissions(extension_.get()); |
| if (!extension_->permissions_data() |
| ->active_permissions() |
| .effective_hosts() |
| .MatchesURL(origin)) |
| return false; |
| std::unique_ptr<const PermissionSet> granted_permissions; |
| std::unique_ptr<const PermissionSet> withheld_permissions; |
| WithholdPermissions(required_permissions, &granted_permissions, |
| &withheld_permissions, true); |
| if (!granted_permissions->effective_hosts().MatchesURL(origin) && |
| withheld_permissions->effective_hosts().MatchesURL(origin)) |
| return true; |
| |
| return false; |
| } |
| |
| void ScriptingPermissionsModifier::RemoveGrantedHostPermission( |
| const GURL& url) { |
| DCHECK(HasGrantedHostPermission(url)); |
| |
| GURL origin = url.GetOrigin(); |
| URLPatternSet explicit_hosts; |
| URLPatternSet scriptable_hosts; |
| const PermissionSet& active_permissions = |
| extension_->permissions_data()->active_permissions(); |
| if (active_permissions.explicit_hosts().MatchesURL(url)) |
| explicit_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(), origin); |
| if (active_permissions.scriptable_hosts().MatchesURL(url)) |
| scriptable_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(), origin); |
| |
| PermissionsUpdater(browser_context_) |
| .RemovePermissions( |
| extension_.get(), |
| PermissionSet(APIPermissionSet(), ManifestPermissionSet(), |
| explicit_hosts, scriptable_hosts), |
| PermissionsUpdater::REMOVE_HARD); |
| } |
| |
| void ScriptingPermissionsModifier::WithholdPermissions( |
| const PermissionSet& permissions, |
| std::unique_ptr<const PermissionSet>* granted_permissions_out, |
| std::unique_ptr<const PermissionSet>* withheld_permissions_out, |
| bool use_initial_state) { |
| bool should_withhold = false; |
| if (CanAffectExtension(permissions)) { |
| if (use_initial_state) { |
| // If the user ever set the extension's "all-urls" preference, then the |
| // initial state was withheld. This is important, since the all-urls |
| // permission should be shown as revokable. Otherwise, default to whatever |
| // the system setting is. |
| should_withhold = HasSetAllowedOnAllUrls() || !DefaultAllowedOnAllUrls(); |
| } else { |
| should_withhold = !IsAllowedOnAllUrls(); |
| } |
| } |
| |
| if (!should_withhold) { |
| *granted_permissions_out = permissions.Clone(); |
| withheld_permissions_out->reset(new PermissionSet()); |
| return; |
| } |
| |
| auto segregate_url_permissions = [](const URLPatternSet& patterns, |
| URLPatternSet* granted, |
| URLPatternSet* withheld) { |
| for (const URLPattern& pattern : patterns) { |
| if (pattern.ImpliesAllHosts()) |
| withheld->AddPattern(pattern); |
| else |
| granted->AddPattern(pattern); |
| } |
| }; |
| |
| URLPatternSet granted_explicit_hosts; |
| URLPatternSet withheld_explicit_hosts; |
| URLPatternSet granted_scriptable_hosts; |
| URLPatternSet withheld_scriptable_hosts; |
| segregate_url_permissions(permissions.explicit_hosts(), |
| &granted_explicit_hosts, &withheld_explicit_hosts); |
| segregate_url_permissions(permissions.scriptable_hosts(), |
| &granted_scriptable_hosts, |
| &withheld_scriptable_hosts); |
| |
| granted_permissions_out->reset( |
| new PermissionSet(permissions.apis(), permissions.manifest_permissions(), |
| granted_explicit_hosts, granted_scriptable_hosts)); |
| withheld_permissions_out->reset( |
| new PermissionSet(APIPermissionSet(), ManifestPermissionSet(), |
| withheld_explicit_hosts, withheld_scriptable_hosts)); |
| } |
| |
| void ScriptingPermissionsModifier::GrantWithheldImpliedAllHosts() { |
| const PermissionSet& withheld = |
| extension_->permissions_data()->withheld_permissions(); |
| |
| PermissionSet permissions( |
| APIPermissionSet(), ManifestPermissionSet(), |
| FilterImpliedAllHostsPatterns(withheld.explicit_hosts()), |
| FilterImpliedAllHostsPatterns(withheld.scriptable_hosts())); |
| PermissionsUpdater(browser_context_) |
| .AddPermissions(extension_.get(), permissions); |
| } |
| |
| void ScriptingPermissionsModifier::WithholdImpliedAllHosts() { |
| const PermissionSet& active = |
| extension_->permissions_data()->active_permissions(); |
| PermissionSet permissions( |
| APIPermissionSet(), ManifestPermissionSet(), |
| FilterImpliedAllHostsPatterns(active.explicit_hosts()), |
| FilterImpliedAllHostsPatterns(active.scriptable_hosts())); |
| PermissionsUpdater(browser_context_) |
| .RemovePermissions(extension_.get(), permissions, |
| PermissionsUpdater::REMOVE_HARD); |
| } |
| |
| void ScriptingPermissionsModifier::CleanUpPrefsIfNecessary() { |
| // From a bug, some extensions such as policy extensions could have the |
| // preference set even if it should have been impossible. Reset the prefs to |
| // a sane state. |
| // See crbug.com/629927 |
| // TODO(devlin): Remove this in M56. |
| DCHECK(ExtensionMustBeAllowedOnAllUrls(*extension_)); |
| extension_prefs_->UpdateExtensionPref(extension_->id(), |
| kExtensionAllowedOnAllUrlsPrefName, |
| base::MakeUnique<base::Value>(true)); |
| extension_prefs_->UpdateExtensionPref( |
| extension_->id(), kHasSetScriptOnAllUrlsPrefName, nullptr); |
| } |
| |
| } // namespace extensions |