blob: 35981d1f479be9ae75cf0acb663626a30a355300 [file] [log] [blame]
[email protected]c333e792012-01-06 16:57:391// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/permissions_updater.h"
6
Ivan Sandrke2b20c62018-09-10 16:23:537#include <set>
dcheng1fc00f12015-12-26 22:18:038#include <utility>
Takashi Toyoshima52f24672019-03-05 05:36:369#include <vector>
dcheng1fc00f12015-12-26 22:18:0310
Takashi Toyoshima52f24672019-03-05 05:36:3611#include "base/barrier_closure.h"
Takashi Toyoshima69579072018-11-19 07:10:5012#include "base/bind.h"
13#include "base/bind_helpers.h"
Devlin Cronin6b492eb2018-08-01 02:37:0914#include "base/feature_list.h"
[email protected]c333e792012-01-06 16:57:3915#include "base/memory/ref_counted.h"
Ivan Sandrke2b20c62018-09-10 16:23:5316#include "base/no_destructor.h"
[email protected]c333e792012-01-06 16:57:3917#include "base/values.h"
[email protected]76aeb1772012-01-20 22:14:1618#include "chrome/browser/extensions/api/permissions/permissions_api_helpers.h"
Oleg Maximenko8fddbf8b2017-08-29 14:19:4819#include "chrome/browser/extensions/extension_management.h"
Takashi Toyoshima69579072018-11-19 07:10:5020#include "chrome/browser/extensions/extension_system_factory.h"
rdevlin.cronincb9f86e2015-10-15 15:13:4221#include "chrome/browser/extensions/scripting_permissions_modifier.h"
[email protected]c333e792012-01-06 16:57:3922#include "chrome/browser/profiles/profile.h"
[email protected]6386cf52012-09-07 04:26:3723#include "chrome/common/extensions/api/permissions.h"
Lei Zhang1e913aa2019-02-08 18:59:2324#include "chrome/common/webui_url_constants.h"
Takashi Toyoshima69579072018-11-19 07:10:5025#include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
26#include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
27#include "content/public/browser/browser_context.h"
[email protected]c97496b2012-10-01 16:20:1928#include "content/public/browser/notification_observer.h"
29#include "content/public/browser/notification_registrar.h"
[email protected]c333e792012-01-06 16:57:3930#include "content/public/browser/notification_service.h"
31#include "content/public/browser/render_process_host.h"
Lei Zhang1e913aa2019-02-08 18:59:2332#include "content/public/common/url_constants.h"
[email protected]34423532013-11-21 18:13:1033#include "extensions/browser/event_router.h"
Takashi Toyoshima69579072018-11-19 07:10:5034#include "extensions/browser/event_router_factory.h"
[email protected]489db0842014-01-22 18:20:0335#include "extensions/browser/extension_prefs.h"
Takashi Toyoshima52f24672019-03-05 05:36:3636#include "extensions/browser/extension_registry.h"
limasdfd0069922015-10-20 17:07:1537#include "extensions/browser/notification_types.h"
Takashi Toyoshima69579072018-11-19 07:10:5038#include "extensions/common/cors_util.h"
[email protected]e4452d32013-11-15 23:07:4139#include "extensions/common/extension.h"
[email protected]fb820c02014-03-13 15:07:0840#include "extensions/common/extension_messages.h"
[email protected]8d42db52014-06-20 21:50:5041#include "extensions/common/manifest_handlers/permissions_parser.h"
42#include "extensions/common/permissions/permission_set.h"
[email protected]e4452d32013-11-15 23:07:4143#include "extensions/common/permissions/permissions_data.h"
[email protected]c333e792012-01-06 16:57:3944
45using content::RenderProcessHost;
[email protected]15f08dd2012-01-27 07:29:4846using extensions::permissions_api_helpers::PackPermissionSet;
[email protected]c333e792012-01-06 16:57:3947
48namespace extensions {
49
[email protected]e054ea12013-08-20 00:41:5750namespace permissions = api::permissions;
[email protected]c333e792012-01-06 16:57:3951
[email protected]23a85362014-07-07 23:26:1952namespace {
53
54// Returns a PermissionSet that has the active permissions of the extension,
55// bounded to its current manifest.
dchengc963c7142016-04-08 03:55:2256std::unique_ptr<const PermissionSet> GetBoundedActivePermissions(
[email protected]e1670582014-08-15 23:05:4157 const Extension* extension,
rdevlin.cronine2d0fd02015-09-24 22:35:4958 const PermissionSet* active_permissions) {
[email protected]23a85362014-07-07 23:26:1959 // If the extension has used the optional permissions API, it will have a
60 // custom set of active permissions defined in the extension prefs. Here,
61 // we update the extension's active permissions based on the prefs.
rdevlin.cronine2d0fd02015-09-24 22:35:4962 if (!active_permissions)
rdevlin.cronind630c302015-09-30 20:19:3363 return extension->permissions_data()->active_permissions().Clone();
[email protected]23a85362014-07-07 23:26:1964
rdevlin.cronind630c302015-09-30 20:19:3365 const PermissionSet& required_permissions =
[email protected]23a85362014-07-07 23:26:1966 PermissionsParser::GetRequiredPermissions(extension);
67
68 // We restrict the active permissions to be within the bounds defined in the
69 // extension's manifest.
70 // a) active permissions must be a subset of optional + default permissions
71 // b) active permissions must contains all default permissions
dchengc963c7142016-04-08 03:55:2272 std::unique_ptr<const PermissionSet> total_permissions =
rdevlin.cronine01ec2c2015-09-17 21:27:2873 PermissionSet::CreateUnion(
rdevlin.cronind630c302015-09-30 20:19:3374 required_permissions,
75 PermissionsParser::GetOptionalPermissions(extension));
[email protected]23a85362014-07-07 23:26:1976
77 // Make sure the active permissions contain no more than optional + default.
dchengc963c7142016-04-08 03:55:2278 std::unique_ptr<const PermissionSet> adjusted_active =
rdevlin.cronine01ec2c2015-09-17 21:27:2879 PermissionSet::CreateIntersection(*total_permissions,
80 *active_permissions);
[email protected]23a85362014-07-07 23:26:1981
82 // Make sure the active permissions contain the default permissions.
rdevlin.cronine01ec2c2015-09-17 21:27:2883 adjusted_active =
rdevlin.cronind630c302015-09-30 20:19:3384 PermissionSet::CreateUnion(required_permissions, *adjusted_active);
[email protected]23a85362014-07-07 23:26:1985
86 return adjusted_active;
87}
88
Ivan Sandrke2b20c62018-09-10 16:23:5389std::unique_ptr<PermissionsUpdater::Delegate>& GetDelegateWrapper() {
90 static base::NoDestructor<std::unique_ptr<PermissionsUpdater::Delegate>>
91 delegate_wrapper;
92 return *delegate_wrapper;
93}
94
95PermissionsUpdater::Delegate* GetDelegate() {
96 return GetDelegateWrapper().get();
97}
isandrk80e3eb92017-04-12 15:22:1498
Takashi Toyoshima69579072018-11-19 07:10:5099// A helper class to watch profile lifetime.
100class PermissionsUpdaterShutdownNotifierFactory
101 : public BrowserContextKeyedServiceShutdownNotifierFactory {
102 public:
103 static PermissionsUpdaterShutdownNotifierFactory* GetInstance() {
104 static base::NoDestructor<PermissionsUpdaterShutdownNotifierFactory>
105 factory;
106 return factory.get();
107 }
108
109 private:
110 friend class base::NoDestructor<PermissionsUpdaterShutdownNotifierFactory>;
111
112 PermissionsUpdaterShutdownNotifierFactory()
113 : BrowserContextKeyedServiceShutdownNotifierFactory(
114 "PermissionsUpdaterShutdownFactory") {
115 DependsOn(EventRouterFactory::GetInstance());
116 DependsOn(ExtensionSystemFactory::GetInstance());
117 }
118 ~PermissionsUpdaterShutdownNotifierFactory() override {}
119
120 DISALLOW_COPY_AND_ASSIGN(PermissionsUpdaterShutdownNotifierFactory);
121};
122
[email protected]23a85362014-07-07 23:26:19123} // namespace
124
Takashi Toyoshima52f24672019-03-05 05:36:36125// A helper class to asynchronously dispatch the event to notify policy host
126// restrictions or permissions once they have been updated. This will fire the
Takashi Toyoshima69579072018-11-19 07:10:50127// event if and only if the BrowserContext is still valid.
128// This class manages its own lifetime and deletes itself when either the
129// permissions updated event is fired, or the BrowserContext is shut down
130// (whichever happens first).
131class PermissionsUpdater::NetworkPermissionsUpdateHelper {
132 public:
Takashi Toyoshima52f24672019-03-05 05:36:36133 static void UpdatePermissions(content::BrowserContext* browser_context,
134 EventType event_type,
135 scoped_refptr<const Extension> extension,
136 const PermissionSet& changed,
137 base::OnceClosure completion_callback);
138
139 static void UpdateDefaultPolicyHostRestrictions(
Takashi Toyoshima69579072018-11-19 07:10:50140 content::BrowserContext* browser_context,
Takashi Toyoshima52f24672019-03-05 05:36:36141 const URLPatternSet& default_runtime_blocked_hosts,
142 const URLPatternSet& default_runtime_allowed_hosts);
Takashi Toyoshima69579072018-11-19 07:10:50143
144 private:
145 // This class manages its own lifetime.
146 NetworkPermissionsUpdateHelper(content::BrowserContext* browser_context,
147 base::OnceClosure dispatch_event);
148 ~NetworkPermissionsUpdateHelper();
149
150 void OnShutdown();
151 void OnOriginAccessUpdated();
152
153 base::OnceClosure dispatch_event_;
154 std::unique_ptr<KeyedServiceShutdownNotifier::Subscription>
155 shutdown_subscription_;
Jeremy Roman495db682019-07-12 16:03:24156 base::WeakPtrFactory<NetworkPermissionsUpdateHelper> weak_factory_{this};
Takashi Toyoshima69579072018-11-19 07:10:50157
158 DISALLOW_COPY_AND_ASSIGN(NetworkPermissionsUpdateHelper);
159};
160
161// static
Takashi Toyoshima52f24672019-03-05 05:36:36162void PermissionsUpdater::NetworkPermissionsUpdateHelper::UpdatePermissions(
163 content::BrowserContext* browser_context,
164 EventType event_type,
165 scoped_refptr<const Extension> extension,
166 const PermissionSet& changed,
167 base::OnceClosure completion_callback) {
168 // If there is no difference in allowlist/blocklist for the extension, we can
169 // synchronously finish it without updating the CORS access list.
170 // We do not apply this optimization for POLICY event_type, since callers do
171 // not pass effective |changed| argument.
172 if (event_type != POLICY && changed.effective_hosts().is_empty()) {
Takashi Toyoshima69579072018-11-19 07:10:50173 PermissionsUpdater::NotifyPermissionsUpdated(
174 browser_context, event_type, std::move(extension), changed.Clone(),
175 std::move(completion_callback));
176 return;
177 }
178
179 std::vector<network::mojom::CorsOriginPatternPtr> allow_list =
Takashi Toyoshima20c20cf2019-02-27 07:39:23180 CreateCorsOriginAccessAllowList(
181 *extension,
182 PermissionsData::EffectiveHostPermissionsMode::kOmitTabSpecific);
Takashi Toyoshima69579072018-11-19 07:10:50183
184 NetworkPermissionsUpdateHelper* helper = new NetworkPermissionsUpdateHelper(
185 browser_context,
186 base::BindOnce(&PermissionsUpdater::NotifyPermissionsUpdated,
187 browser_context, event_type, extension,
188 changed.Clone(), std::move(completion_callback)));
189
190 // After an asynchronous call below, the helper will call
191 // NotifyPermissionsUpdated if the profile is still valid.
Takashi Toyoshima0190a3b2019-01-21 07:39:06192 browser_context->SetCorsOriginAccessListForOrigin(
193 url::Origin::Create(extension->url()), std::move(allow_list),
194 CreateCorsOriginAccessBlockList(*extension),
Takashi Toyoshima69579072018-11-19 07:10:50195 base::BindOnce(&NetworkPermissionsUpdateHelper::OnOriginAccessUpdated,
196 helper->weak_factory_.GetWeakPtr()));
gpdavis.chromium0fbac4d2014-09-19 20:57:54197}
198
Takashi Toyoshima52f24672019-03-05 05:36:36199// static
200void PermissionsUpdater::NetworkPermissionsUpdateHelper::
201 UpdateDefaultPolicyHostRestrictions(
202 content::BrowserContext* browser_context,
203 const URLPatternSet& default_runtime_blocked_hosts,
204 const URLPatternSet& default_runtime_allowed_hosts) {
205 NetworkPermissionsUpdateHelper* helper = new NetworkPermissionsUpdateHelper(
206 browser_context,
207 base::BindOnce(
208 &PermissionsUpdater::NotifyDefaultPolicyHostRestrictionsUpdated,
209 browser_context, default_runtime_blocked_hosts.Clone(),
210 default_runtime_allowed_hosts.Clone()));
211
212 const ExtensionSet& extensions =
213 ExtensionRegistry::Get(browser_context)->enabled_extensions();
214 base::RepeatingClosure barrier_closure = base::BarrierClosure(
215 extensions.size(),
216 base::BindOnce(&NetworkPermissionsUpdateHelper::OnOriginAccessUpdated,
217 helper->weak_factory_.GetWeakPtr()));
218
219 for (const auto& extension : extensions) {
220 std::vector<network::mojom::CorsOriginPatternPtr> allow_list =
221 CreateCorsOriginAccessAllowList(
222 *extension,
223 PermissionsData::EffectiveHostPermissionsMode::kOmitTabSpecific);
Takashi Toyoshima52f24672019-03-05 05:36:36224 browser_context->SetCorsOriginAccessListForOrigin(
225 url::Origin::Create(extension->url()), std::move(allow_list),
226 CreateCorsOriginAccessBlockList(*extension), barrier_closure);
227 }
228}
229
Takashi Toyoshima69579072018-11-19 07:10:50230PermissionsUpdater::NetworkPermissionsUpdateHelper::
231 NetworkPermissionsUpdateHelper(content::BrowserContext* browser_context,
232 base::OnceClosure dispatch_event)
233 : dispatch_event_(std::move(dispatch_event)),
234 shutdown_subscription_(
235 PermissionsUpdaterShutdownNotifierFactory::GetInstance()
236 ->Get(browser_context)
237 ->Subscribe(
238 base::Bind(&NetworkPermissionsUpdateHelper::OnShutdown,
Jeremy Roman495db682019-07-12 16:03:24239 base::Unretained(this)))) {}
Takashi Toyoshima69579072018-11-19 07:10:50240
241PermissionsUpdater::NetworkPermissionsUpdateHelper::
242 ~NetworkPermissionsUpdateHelper() {}
243
244void PermissionsUpdater::NetworkPermissionsUpdateHelper::OnShutdown() {
245 // The profile is shutting down. Don't dispatch the permissions updated
246 // event, and clean up the dangling references.
247 delete this;
248}
249
250void PermissionsUpdater::NetworkPermissionsUpdateHelper::
251 OnOriginAccessUpdated() {
252 // The origin access list was successfully updated; dispatch the event
253 // and clean up dangling references.
254 std::move(dispatch_event_).Run();
255 delete this;
256}
257
258PermissionsUpdater::PermissionsUpdater(content::BrowserContext* browser_context)
259 : PermissionsUpdater(browser_context, INIT_FLAG_NONE) {}
260
gpdavis.chromium0fbac4d2014-09-19 20:57:54261PermissionsUpdater::PermissionsUpdater(content::BrowserContext* browser_context,
262 InitFlag init_flag)
Takashi Toyoshima69579072018-11-19 07:10:50263 : browser_context_(browser_context), init_flag_(init_flag) {}
[email protected]c333e792012-01-06 16:57:39264
265PermissionsUpdater::~PermissionsUpdater() {}
266
isandrk80e3eb92017-04-12 15:22:14267// static
Ivan Sandrke2b20c62018-09-10 16:23:53268void PermissionsUpdater::SetPlatformDelegate(
269 std::unique_ptr<Delegate> delegate) {
270 GetDelegateWrapper() = std::move(delegate);
isandrk80e3eb92017-04-12 15:22:14271}
272
Devlin Cronin5218d262018-07-09 20:18:05273void PermissionsUpdater::GrantOptionalPermissions(
274 const Extension& extension,
Takashi Toyoshima69579072018-11-19 07:10:50275 const PermissionSet& permissions,
276 base::OnceClosure completion_callback) {
Devlin Croninf8cde092018-11-30 22:17:13277 CHECK(PermissionsParser::GetOptionalPermissions(&extension)
278 .Contains(permissions))
279 << "Cannot add optional permissions that are not "
280 << "specified in the manifest.";
[email protected]c333e792012-01-06 16:57:39281
Devlin Cronin5218d262018-07-09 20:18:05282 // Granted optional permissions are stored in both the granted permissions (so
283 // we don't later disable the extension when we check the active permissions
284 // against the granted set to determine if there's a permissions increase) and
285 // the granted runtime permissions (so they don't get withheld with runtime
Devlin Cronin6b492eb2018-08-01 02:37:09286 // host permissions enabled). They're also added to the active set, which is
287 // the permission set stored in preferences representing the extension's
288 // currently-desired permission state.
Devlin Cronin5218d262018-07-09 20:18:05289 constexpr int permissions_store_mask =
Devlin Cronin6b492eb2018-08-01 02:37:09290 kActivePermissions | kGrantedPermissions | kRuntimeGrantedPermissions;
291 AddPermissionsImpl(extension, permissions, permissions_store_mask,
Takashi Toyoshima69579072018-11-19 07:10:50292 permissions, std::move(completion_callback));
[email protected]c333e792012-01-06 16:57:39293}
294
Devlin Cronin5218d262018-07-09 20:18:05295void PermissionsUpdater::GrantRuntimePermissions(
296 const Extension& extension,
Takashi Toyoshima69579072018-11-19 07:10:50297 const PermissionSet& permissions,
298 base::OnceClosure completion_callback) {
Devlin Cronin6b492eb2018-08-01 02:37:09299 // We don't want to grant the extension object/process more privilege than it
300 // requested, even if the user grants additional permission. For instance, if
301 // the extension requests https://maps.google.com and the user grants
302 // https://*.google.com, we only want to grant the extension itself
303 // https://maps.google.com. Since we updated the prefs with the exact
304 // granted permissions (*.google.com), if the extension later requests
305 // increased permissions that are already covered, they will be auto-granted.
306
307 // Determine which permissions to add to the extension.
308 const PermissionSet& withheld =
309 extension.permissions_data()->withheld_permissions();
310
311 // We add the intersection of any permissions that were withheld and the
312 // permissions that were granted. Since these might not be directly
313 // overlapping, we need to use a detailed intersection behavior here.
314 std::unique_ptr<const PermissionSet> active_permissions_to_add =
315 PermissionSet::CreateIntersection(
316 withheld, permissions,
317 URLPatternSet::IntersectionBehavior::kDetailed);
Devlin Cronin5218d262018-07-09 20:18:05318 CHECK(extension.permissions_data()->withheld_permissions().Contains(
Devlin Cronin6b492eb2018-08-01 02:37:09319 *active_permissions_to_add))
Devlin Cronin5218d262018-07-09 20:18:05320 << "Cannot add runtime granted permissions that were not withheld.";
rdevlin.cronin77cb0ef2015-09-16 17:03:48321
Devlin Cronin5218d262018-07-09 20:18:05322 // Adding runtime granted permissions does not add permissions to the
Devlin Cronin6b492eb2018-08-01 02:37:09323 // granted or active permissions store, so that behavior taken with the
324 // runtime host permissions feature is confined to when the experiment is
325 // enabled.
Devlin Cronin5218d262018-07-09 20:18:05326 constexpr int permissions_store_mask = kRuntimeGrantedPermissions;
Devlin Cronin6b492eb2018-08-01 02:37:09327 AddPermissionsImpl(extension, *active_permissions_to_add,
Takashi Toyoshima69579072018-11-19 07:10:50328 permissions_store_mask, permissions,
329 std::move(completion_callback));
Devlin Cronin5218d262018-07-09 20:18:05330}
rdevlin.cronin77cb0ef2015-09-16 17:03:48331
Devlin Cronin5218d262018-07-09 20:18:05332void PermissionsUpdater::RevokeOptionalPermissions(
333 const Extension& extension,
334 const PermissionSet& permissions,
Takashi Toyoshima69579072018-11-19 07:10:50335 RemoveType remove_type,
336 base::OnceClosure completion_callback) {
Devlin Cronin5218d262018-07-09 20:18:05337 // TODO(devlin): Ideally, we'd have this CHECK in place, but unit tests are
338 // currently violating it.
Devlin Croninf8cde092018-11-30 22:17:13339 CHECK(PermissionsParser::GetOptionalPermissions(&extension)
340 .Contains(permissions))
341 << "Cannot remove optional permissions that are not "
342 << "specified in the manifest.";
rdevlin.cronin77cb0ef2015-09-16 17:03:48343
Devlin Cronin5218d262018-07-09 20:18:05344 // Revoked optional permissions are removed from granted and runtime-granted
345 // permissions only if the user, and not the extension, removed them. This
Devlin Cronin6b492eb2018-08-01 02:37:09346 // allows the extension to add them again without prompting the user. They are
347 // always removed from the active set, which is the set of permissions the
348 // the extension currently requests.
349 int permissions_store_mask = kActivePermissions;
Devlin Cronin5218d262018-07-09 20:18:05350 if (remove_type == REMOVE_HARD)
Devlin Cronin6b492eb2018-08-01 02:37:09351 permissions_store_mask |= kGrantedPermissions | kRuntimeGrantedPermissions;
352
353 RemovePermissionsImpl(extension, permissions, permissions_store_mask,
Takashi Toyoshima69579072018-11-19 07:10:50354 permissions, std::move(completion_callback));
Devlin Cronin5218d262018-07-09 20:18:05355}
rdevlin.cronin77cb0ef2015-09-16 17:03:48356
Devlin Cronin5218d262018-07-09 20:18:05357void PermissionsUpdater::RevokeRuntimePermissions(
358 const Extension& extension,
Takashi Toyoshima69579072018-11-19 07:10:50359 const PermissionSet& permissions,
360 base::OnceClosure completion_callback) {
Devlin Cronin6b492eb2018-08-01 02:37:09361 // Similar to the process in adding permissions, we might be revoking more
362 // permissions than the extension currently has explicit access to. For
363 // instance, we might be revoking https://*.google.com/* even if the extension
364 // only has https://maps.google.com/*.
365 const PermissionSet& active =
366 extension.permissions_data()->active_permissions();
367 // Unlike adding permissions, we should know that any permissions we remove
368 // are a superset of the permissions the extension has active (because we only
369 // allow removal origins and the extension can't have a broader origin than
370 // what it has granted).
371 std::unique_ptr<const PermissionSet> active_permissions_to_remove =
372 PermissionSet::CreateIntersection(
373 active, permissions,
374 URLPatternSet::IntersectionBehavior::kPatternsContainedByBoth);
375 // One exception: If we're revoking a permission like "<all_urls>", we need
376 // to make sure it doesn't revoke the included chrome://favicon permission.
377 std::set<URLPattern> removable_explicit_hosts;
378 bool needs_adjustment = false;
379 for (const auto& pattern : active_permissions_to_remove->explicit_hosts()) {
Lei Zhang1e913aa2019-02-08 18:59:23380 bool is_chrome_favicon = pattern.scheme() == content::kChromeUIScheme &&
381 pattern.host() == chrome::kChromeUIFaviconHost;
382 if (is_chrome_favicon)
Devlin Cronin6b492eb2018-08-01 02:37:09383 needs_adjustment = true;
384 else
385 removable_explicit_hosts.insert(pattern);
386 }
387 if (needs_adjustment) {
388 // Tedious, because PermissionSets are const. :(
389 active_permissions_to_remove = std::make_unique<PermissionSet>(
Devlin Cronin32708b02018-12-05 17:58:04390 active_permissions_to_remove->apis().Clone(),
391 active_permissions_to_remove->manifest_permissions().Clone(),
Devlin Cronin6b492eb2018-08-01 02:37:09392 URLPatternSet(removable_explicit_hosts),
Devlin Cronin2db2bd42019-02-25 19:43:47393 active_permissions_to_remove->scriptable_hosts().Clone());
Devlin Cronin6b492eb2018-08-01 02:37:09394 }
395
396 CHECK(extension.permissions_data()->active_permissions().Contains(
397 *active_permissions_to_remove))
398 << "Cannot remove permissions that are not active.";
Devlin Cronin5218d262018-07-09 20:18:05399 CHECK(GetRevokablePermissions(&extension)->Contains(permissions))
400 << "Cannot remove non-revokable permissions.";
rdevlin.cronin77cb0ef2015-09-16 17:03:48401
Devlin Cronin5218d262018-07-09 20:18:05402 // Removing runtime-granted permissions does not remove permissions from
403 // the granted permissions store. This is done to ensure behavior taken with
404 // the runtime host permissions feature is confined to when the experiment is
Devlin Cronin6b492eb2018-08-01 02:37:09405 // enabled. Similarly, since the runtime-granted permissions were never added
406 // to the active permissions stored in prefs, they are also not removed.
Devlin Cronin5218d262018-07-09 20:18:05407 constexpr int permissions_store_mask = kRuntimeGrantedPermissions;
Devlin Cronin6b492eb2018-08-01 02:37:09408 RemovePermissionsImpl(extension, *active_permissions_to_remove,
Takashi Toyoshima69579072018-11-19 07:10:50409 permissions_store_mask, permissions,
410 std::move(completion_callback));
rdevlin.cronin77cb0ef2015-09-16 17:03:48411}
412
nrpetere33d2a5b2017-04-25 00:12:31413void PermissionsUpdater::SetPolicyHostRestrictions(
414 const Extension* extension,
415 const URLPatternSet& runtime_blocked_hosts,
416 const URLPatternSet& runtime_allowed_hosts) {
417 extension->permissions_data()->SetPolicyHostRestrictions(
418 runtime_blocked_hosts, runtime_allowed_hosts);
419
Takashi Toyoshima69579072018-11-19 07:10:50420 // Update the BrowserContext origin lists, and send notification to the
421 // currently running renderers of the runtime block hosts settings.
Takashi Toyoshima52f24672019-03-05 05:36:36422 NetworkPermissionsUpdateHelper::UpdatePermissions(browser_context_, POLICY,
423 extension, PermissionSet(),
424 base::DoNothing::Once());
nrpetere33d2a5b2017-04-25 00:12:31425}
426
427void PermissionsUpdater::SetUsesDefaultHostRestrictions(
428 const Extension* extension) {
429 extension->permissions_data()->SetUsesDefaultHostRestrictions();
Takashi Toyoshima52f24672019-03-05 05:36:36430 NetworkPermissionsUpdateHelper::UpdatePermissions(browser_context_, POLICY,
431 extension, PermissionSet(),
432 base::DoNothing::Once());
nrpetere33d2a5b2017-04-25 00:12:31433}
434
435void PermissionsUpdater::SetDefaultPolicyHostRestrictions(
436 const URLPatternSet& default_runtime_blocked_hosts,
437 const URLPatternSet& default_runtime_allowed_hosts) {
Takashi Toyoshima52f24672019-03-05 05:36:36438 DCHECK_EQ(0, init_flag_ & INIT_FLAG_TRANSIENT);
439
nrpetere33d2a5b2017-04-25 00:12:31440 PermissionsData::SetDefaultPolicyHostRestrictions(
441 default_runtime_blocked_hosts, default_runtime_allowed_hosts);
442
Takashi Toyoshima52f24672019-03-05 05:36:36443 // Update the BrowserContext origin lists, and send notification to the
444 // currently running renderers of the runtime block hosts settings.
445 NetworkPermissionsUpdateHelper::UpdateDefaultPolicyHostRestrictions(
446 browser_context_, default_runtime_blocked_hosts,
447 default_runtime_allowed_hosts);
nrpetere33d2a5b2017-04-25 00:12:31448}
449
rdevlin.cronin77cb0ef2015-09-16 17:03:48450void PermissionsUpdater::RemovePermissionsUnsafe(
451 const Extension* extension,
rdevlin.cronind630c302015-09-30 20:19:33452 const PermissionSet& to_remove) {
453 const PermissionSet& active =
rdevlin.cronine2d0fd02015-09-24 22:35:49454 extension->permissions_data()->active_permissions();
dchengc963c7142016-04-08 03:55:22455 std::unique_ptr<const PermissionSet> total =
rdevlin.cronind630c302015-09-30 20:19:33456 PermissionSet::CreateDifference(active, to_remove);
rdevlin.cronin77cb0ef2015-09-16 17:03:48457 // |successfully_removed| might not equal |to_remove| if |to_remove| contains
458 // permissions the extension didn't have.
dchengc963c7142016-04-08 03:55:22459 std::unique_ptr<const PermissionSet> successfully_removed =
rdevlin.cronind630c302015-09-30 20:19:33460 PermissionSet::CreateDifference(active, *total);
[email protected]c333e792012-01-06 16:57:39461
Devlin Cronin6b492eb2018-08-01 02:37:09462 // TODO(devlin): This seems wrong. Since these permissions are being removed
463 // by enterprise policy, we should not update the active permissions set in
464 // preferences. That way, if the enterprise policy is changed, the removed
465 // permissions would be re-added.
466 constexpr bool update_active_prefs = true;
467 SetPermissions(extension, std::move(total), update_active_prefs);
Takashi Toyoshima52f24672019-03-05 05:36:36468 NetworkPermissionsUpdateHelper::UpdatePermissions(
Takashi Toyoshima69579072018-11-19 07:10:50469 browser_context_, REMOVED, extension, *successfully_removed,
470 base::DoNothing::Once());
rdevlin.cronin77cb0ef2015-09-16 17:03:48471}
[email protected]c333e792012-01-06 16:57:39472
dchengc963c7142016-04-08 03:55:22473std::unique_ptr<const PermissionSet>
474PermissionsUpdater::GetRevokablePermissions(const Extension* extension) const {
Devlin Croninf355f1de2018-05-14 15:27:24475 // Any permissions not required by the extension are revokable.
rdevlin.cronincb9f86e2015-10-15 15:13:42476 const PermissionSet& required =
477 PermissionsParser::GetRequiredPermissions(extension);
Devlin Croninf355f1de2018-05-14 15:27:24478 std::unique_ptr<const PermissionSet> revokable_permissions =
479 PermissionSet::CreateDifference(
480 extension->permissions_data()->active_permissions(), required);
481
482 // Additionally, some required permissions may be revokable if they can be
483 // withheld by the ScriptingPermissionsModifier.
484 std::unique_ptr<const PermissionSet> revokable_scripting_permissions =
485 ScriptingPermissionsModifier(browser_context_,
486 base::WrapRefCounted(extension))
487 .GetRevokablePermissions();
488
489 if (revokable_scripting_permissions) {
490 revokable_permissions = PermissionSet::CreateUnion(
491 *revokable_permissions, *revokable_scripting_permissions);
492 }
493 return revokable_permissions;
[email protected]c333e792012-01-06 16:57:39494}
495
[email protected]009633c2013-03-07 22:08:28496void PermissionsUpdater::GrantActivePermissions(const Extension* extension) {
[email protected]c333e792012-01-06 16:57:39497 CHECK(extension);
498
rdevlin.cronine2d0fd02015-09-24 22:35:49499 ExtensionPrefs::Get(browser_context_)
500 ->AddGrantedPermissions(
501 extension->id(), extension->permissions_data()->active_permissions());
[email protected]f746b3f2012-07-03 17:53:37502}
503
[email protected]23a85362014-07-07 23:26:19504void PermissionsUpdater::InitializePermissions(const Extension* extension) {
dchengc963c7142016-04-08 03:55:22505 std::unique_ptr<const PermissionSet> bounded_wrapper;
rdevlin.cronine2d0fd02015-09-24 22:35:49506 const PermissionSet* bounded_active = nullptr;
Devlin Cronin582db61d2018-05-17 00:38:31507 ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
gpdavis.chromium0fbac4d2014-09-19 20:57:54508 // If |extension| is a transient dummy extension, we do not want to look for
509 // it in preferences.
510 if (init_flag_ & INIT_FLAG_TRANSIENT) {
rdevlin.cronincb9f86e2015-10-15 15:13:42511 bounded_active = &extension->permissions_data()->active_permissions();
gpdavis.chromium0fbac4d2014-09-19 20:57:54512 } else {
dchengc963c7142016-04-08 03:55:22513 std::unique_ptr<const PermissionSet> active_permissions =
Devlin Cronin582db61d2018-05-17 00:38:31514 prefs->GetActivePermissions(extension->id());
rdevlin.cronine2d0fd02015-09-24 22:35:49515 bounded_wrapper =
rdevlin.cronincb9f86e2015-10-15 15:13:42516 GetBoundedActivePermissions(extension, active_permissions.get());
rdevlin.cronine2d0fd02015-09-24 22:35:49517 bounded_active = bounded_wrapper.get();
gpdavis.chromium0fbac4d2014-09-19 20:57:54518 }
[email protected]8d42db52014-06-20 21:50:50519
Tim Judkins6c4b2302019-08-21 02:00:21520 std::unique_ptr<const PermissionSet> granted_permissions =
521 ScriptingPermissionsModifier::WithholdPermissionsIfNecessary(
522 *extension, *prefs, *bounded_active);
[email protected]8d42db52014-06-20 21:50:50523
Ivan Sandrke2b20c62018-09-10 16:23:53524 if (GetDelegate())
525 GetDelegate()->InitializePermissions(extension, &granted_permissions);
isandrk80e3eb92017-04-12 15:22:14526
Devlin Cronin6b492eb2018-08-01 02:37:09527 bool update_active_permissions = false;
Oleg Maximenko8fddbf8b2017-08-29 14:19:48528 if ((init_flag_ & INIT_FLAG_TRANSIENT) == 0) {
Devlin Cronin6b492eb2018-08-01 02:37:09529 update_active_permissions = true;
Oleg Maximenko8fddbf8b2017-08-29 14:19:48530 // Apply per-extension policy if set.
531 ExtensionManagement* management =
532 ExtensionManagementFactory::GetForBrowserContext(browser_context_);
Devlin Cronin7e0f41ff2018-05-16 17:19:36533 if (!management->UsesDefaultPolicyHostRestrictions(extension)) {
Oleg Maximenko8fddbf8b2017-08-29 14:19:48534 SetPolicyHostRestrictions(extension,
Devlin Cronin7e0f41ff2018-05-16 17:19:36535 management->GetPolicyBlockedHosts(extension),
536 management->GetPolicyAllowedHosts(extension));
Oleg Maximenko8fddbf8b2017-08-29 14:19:48537 }
538 }
539
dcheng1fc00f12015-12-26 22:18:03540 SetPermissions(extension, std::move(granted_permissions),
Devlin Cronin6b492eb2018-08-01 02:37:09541 update_active_permissions);
[email protected]23a85362014-07-07 23:26:19542}
543
Devlin Cronin5218d262018-07-09 20:18:05544void PermissionsUpdater::AddPermissionsForTesting(
545 const Extension& extension,
546 const PermissionSet& permissions) {
Takashi Toyoshima69579072018-11-19 07:10:50547 AddPermissionsImpl(extension, permissions, kNone, permissions,
548 base::DoNothing::Once());
Devlin Cronin5218d262018-07-09 20:18:05549}
550
[email protected]23a85362014-07-07 23:26:19551void PermissionsUpdater::SetPermissions(
552 const Extension* extension,
Devlin Cronin6b492eb2018-08-01 02:37:09553 std::unique_ptr<const PermissionSet> new_active,
554 bool update_prefs) {
555 // Calculate the withheld permissions as any permissions that were required,
556 // but are not in the active set.
557 const PermissionSet& required =
558 PermissionsParser::GetRequiredPermissions(extension);
559 // TODO(https://crbug.com/869403): Currently, withheld permissions should only
560 // contain permissions withheld by the runtime host permissions feature.
561 // However, there could possibly be API permissions that were removed from the
562 // active set by enterprise policy. These shouldn't go in the withheld
563 // permission set, since withheld permissions are generally supposed to be
564 // grantable. Currently, we can deal with this because all permissions
565 // withheld by runtime host permissions are explicit or scriptable hosts, and
566 // all permissions blocked by enterprise are API permissions. So to get the
567 // set of runtime-hosts-withheld permissions, we just look at the delta in the
568 // URLPatternSets. However, this is very fragile, and should be dealt with
569 // more robustly.
570 std::unique_ptr<const PermissionSet> new_withheld =
571 PermissionSet::CreateDifference(
572 PermissionSet(APIPermissionSet(), ManifestPermissionSet(),
Devlin Cronin2db2bd42019-02-25 19:43:47573 required.explicit_hosts().Clone(),
574 required.scriptable_hosts().Clone()),
Devlin Cronin6b492eb2018-08-01 02:37:09575 *new_active);
rdevlin.cronine2d0fd02015-09-24 22:35:49576
Devlin Cronin6b492eb2018-08-01 02:37:09577 extension->permissions_data()->SetPermissions(std::move(new_active),
578 std::move(new_withheld));
579
580 if (update_prefs) {
gpdavis.chromium0fbac4d2014-09-19 20:57:54581 ExtensionPrefs::Get(browser_context_)
Devlin Cronin6b492eb2018-08-01 02:37:09582 ->SetActivePermissions(
583 extension->id(),
584 extension->permissions_data()->active_permissions());
gpdavis.chromium0fbac4d2014-09-19 20:57:54585 }
[email protected]c333e792012-01-06 16:57:39586}
587
Takashi Toyoshima69579072018-11-19 07:10:50588// static
[email protected]c333e792012-01-06 16:57:39589void PermissionsUpdater::NotifyPermissionsUpdated(
Takashi Toyoshima69579072018-11-19 07:10:50590 content::BrowserContext* browser_context,
[email protected]c333e792012-01-06 16:57:39591 EventType event_type,
Takashi Toyoshima69579072018-11-19 07:10:50592 scoped_refptr<const Extension> extension,
593 std::unique_ptr<const PermissionSet> changed,
594 base::OnceClosure completion_callback) {
595 if (changed->IsEmpty() && event_type != POLICY) {
596 std::move(completion_callback).Run();
[email protected]c333e792012-01-06 16:57:39597 return;
Takashi Toyoshima69579072018-11-19 07:10:50598 }
[email protected]c333e792012-01-06 16:57:39599
600 UpdatedExtensionPermissionsInfo::Reason reason;
nrpetere33d2a5b2017-04-25 00:12:31601 events::HistogramValue histogram_value = events::UNKNOWN;
[email protected]c333e792012-01-06 16:57:39602 const char* event_name = NULL;
Takashi Toyoshima69579072018-11-19 07:10:50603 Profile* profile = Profile::FromBrowserContext(browser_context);
[email protected]c333e792012-01-06 16:57:39604
605 if (event_type == REMOVED) {
606 reason = UpdatedExtensionPermissionsInfo::REMOVED;
kalmanef20c652015-07-06 22:18:33607 histogram_value = events::PERMISSIONS_ON_REMOVED;
[email protected]e054ea12013-08-20 00:41:57608 event_name = permissions::OnRemoved::kEventName;
nrpetere33d2a5b2017-04-25 00:12:31609 } else if (event_type == ADDED) {
[email protected]c333e792012-01-06 16:57:39610 reason = UpdatedExtensionPermissionsInfo::ADDED;
kalmanef20c652015-07-06 22:18:33611 histogram_value = events::PERMISSIONS_ON_ADDED;
[email protected]e054ea12013-08-20 00:41:57612 event_name = permissions::OnAdded::kEventName;
nrpetere33d2a5b2017-04-25 00:12:31613 } else {
614 DCHECK_EQ(POLICY, event_type);
615 reason = UpdatedExtensionPermissionsInfo::POLICY;
[email protected]c333e792012-01-06 16:57:39616 }
617
618 // Notify other APIs or interested parties.
nrpetere33d2a5b2017-04-25 00:12:31619 UpdatedExtensionPermissionsInfo info =
Takashi Toyoshima69579072018-11-19 07:10:50620 UpdatedExtensionPermissionsInfo(extension.get(), *changed, reason);
[email protected]c333e792012-01-06 16:57:39621 content::NotificationService::current()->Notify(
[email protected]adf5a102014-07-31 12:44:06622 extensions::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED,
[email protected]8d42db52014-06-20 21:50:50623 content::Source<Profile>(profile),
[email protected]c333e792012-01-06 16:57:39624 content::Details<UpdatedExtensionPermissionsInfo>(&info));
625
[email protected]8d42db52014-06-20 21:50:50626 ExtensionMsg_UpdatePermissions_Params params;
[email protected]8d42db52014-06-20 21:50:50627 params.extension_id = extension->id();
[email protected]23a85362014-07-07 23:26:19628 params.active_permissions = ExtensionMsg_PermissionSetStruct(
rdevlin.cronind630c302015-09-30 20:19:33629 extension->permissions_data()->active_permissions());
[email protected]23a85362014-07-07 23:26:19630 params.withheld_permissions = ExtensionMsg_PermissionSetStruct(
rdevlin.cronind630c302015-09-30 20:19:33631 extension->permissions_data()->withheld_permissions());
nrpetere33d2a5b2017-04-25 00:12:31632 params.uses_default_policy_host_restrictions =
633 extension->permissions_data()->UsesDefaultPolicyHostRestrictions();
634 if (!params.uses_default_policy_host_restrictions) {
635 params.policy_blocked_hosts =
Devlin Cronin11860c982018-12-14 20:18:27636 extension->permissions_data()->policy_blocked_hosts().Clone();
nrpetere33d2a5b2017-04-25 00:12:31637 params.policy_allowed_hosts =
Devlin Cronin11860c982018-12-14 20:18:27638 extension->permissions_data()->policy_allowed_hosts().Clone();
nrpetere33d2a5b2017-04-25 00:12:31639 }
[email protected]8d42db52014-06-20 21:50:50640
[email protected]c333e792012-01-06 16:57:39641 // Send the new permissions to the renderers.
642 for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator());
643 !i.IsAtEnd(); i.Advance()) {
644 RenderProcessHost* host = i.GetCurrentValue();
[email protected]8d42db52014-06-20 21:50:50645 if (profile->IsSameProfile(
646 Profile::FromBrowserContext(host->GetBrowserContext()))) {
647 host->Send(new ExtensionMsg_UpdatePermissions(params));
[email protected]e737c442013-11-15 15:55:24648 }
[email protected]c333e792012-01-06 16:57:39649 }
650
nrpetere33d2a5b2017-04-25 00:12:31651 // Trigger the onAdded and onRemoved events in the extension. We explicitly
652 // don't do this for policy-related events.
Takashi Toyoshima69579072018-11-19 07:10:50653 EventRouter* event_router =
654 event_name ? EventRouter::Get(browser_context) : nullptr;
655 if (event_router) {
656 std::unique_ptr<base::ListValue> value(new base::ListValue());
657 std::unique_ptr<api::permissions::Permissions> permissions =
658 PackPermissionSet(*changed);
659 value->Append(permissions->ToValue());
660 auto event = std::make_unique<Event>(histogram_value, event_name,
661 std::move(value), browser_context);
662 event_router->DispatchEventToExtension(extension->id(), std::move(event));
663 }
664
665 std::move(completion_callback).Run();
nrpetere33d2a5b2017-04-25 00:12:31666}
667
Takashi Toyoshima52f24672019-03-05 05:36:36668// static
nrpetere33d2a5b2017-04-25 00:12:31669void PermissionsUpdater::NotifyDefaultPolicyHostRestrictionsUpdated(
Takashi Toyoshima52f24672019-03-05 05:36:36670 content::BrowserContext* browser_context,
671 const URLPatternSet default_runtime_blocked_hosts,
672 const URLPatternSet default_runtime_allowed_hosts) {
673 Profile* profile = Profile::FromBrowserContext(browser_context);
nrpetere33d2a5b2017-04-25 00:12:31674
675 ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params params;
Devlin Cronin11860c982018-12-14 20:18:27676 params.default_policy_blocked_hosts = default_runtime_blocked_hosts.Clone();
677 params.default_policy_allowed_hosts = default_runtime_allowed_hosts.Clone();
nrpetere33d2a5b2017-04-25 00:12:31678
679 // Send the new policy to the renderers.
680 for (RenderProcessHost::iterator host_iterator(
681 RenderProcessHost::AllHostsIterator());
682 !host_iterator.IsAtEnd(); host_iterator.Advance()) {
683 RenderProcessHost* host = host_iterator.GetCurrentValue();
684 if (profile->IsSameProfile(
685 Profile::FromBrowserContext(host->GetBrowserContext()))) {
686 host->Send(new ExtensionMsg_UpdateDefaultPolicyHostRestrictions(params));
687 }
688 }
[email protected]c333e792012-01-06 16:57:39689}
690
Devlin Cronin6b492eb2018-08-01 02:37:09691void PermissionsUpdater::AddPermissionsImpl(
692 const Extension& extension,
693 const PermissionSet& active_permissions_to_add,
694 int permissions_store_mask,
Takashi Toyoshima69579072018-11-19 07:10:50695 const PermissionSet& prefs_permissions_to_add,
696 base::OnceClosure completion_callback) {
Devlin Cronin6b492eb2018-08-01 02:37:09697 std::unique_ptr<const PermissionSet> new_active = PermissionSet::CreateUnion(
698 active_permissions_to_add,
699 extension.permissions_data()->active_permissions());
Devlin Cronin5218d262018-07-09 20:18:05700
Devlin Cronin6b492eb2018-08-01 02:37:09701 bool update_active_prefs = (permissions_store_mask & kActivePermissions) != 0;
702 SetPermissions(&extension, std::move(new_active), update_active_prefs);
Devlin Cronin5218d262018-07-09 20:18:05703
704 if ((permissions_store_mask & kGrantedPermissions) != 0) {
705 // TODO(devlin): Could we only grant |permissions|, rather than all those
706 // in the active permissions? In theory, all other active permissions have
707 // already been granted.
708 GrantActivePermissions(&extension);
709 }
710
711 if ((permissions_store_mask & kRuntimeGrantedPermissions) != 0) {
712 ExtensionPrefs::Get(browser_context_)
Devlin Cronin6b492eb2018-08-01 02:37:09713 ->AddRuntimeGrantedPermissions(extension.id(),
714 prefs_permissions_to_add);
Devlin Cronin5218d262018-07-09 20:18:05715 }
716
Takashi Toyoshima52f24672019-03-05 05:36:36717 NetworkPermissionsUpdateHelper::UpdatePermissions(
Takashi Toyoshima69579072018-11-19 07:10:50718 browser_context_, ADDED, &extension, active_permissions_to_add,
719 std::move(completion_callback));
Devlin Cronin5218d262018-07-09 20:18:05720}
721
Devlin Cronin6b492eb2018-08-01 02:37:09722void PermissionsUpdater::RemovePermissionsImpl(
723 const Extension& extension,
724 const PermissionSet& active_permissions_to_remove,
725 int permissions_store_mask,
Takashi Toyoshima69579072018-11-19 07:10:50726 const PermissionSet& prefs_permissions_to_remove,
727 base::OnceClosure completion_callback) {
Devlin Cronin6b492eb2018-08-01 02:37:09728 std::unique_ptr<const PermissionSet> new_active =
Devlin Cronin5218d262018-07-09 20:18:05729 PermissionSet::CreateDifference(
Devlin Cronin6b492eb2018-08-01 02:37:09730 extension.permissions_data()->active_permissions(),
731 active_permissions_to_remove);
Devlin Cronin5218d262018-07-09 20:18:05732
Devlin Cronin6b492eb2018-08-01 02:37:09733 bool update_active_prefs = (permissions_store_mask & kActivePermissions) != 0;
734 SetPermissions(&extension, std::move(new_active), update_active_prefs);
Devlin Cronin5218d262018-07-09 20:18:05735
736 ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
737 // NOTE: Currently, this code path is only reached in unit tests. See comment
738 // above REMOVE_HARD in the header file.
Devlin Cronin6b492eb2018-08-01 02:37:09739 if ((permissions_store_mask & kGrantedPermissions) != 0) {
740 prefs->RemoveGrantedPermissions(extension.id(),
741 prefs_permissions_to_remove);
742 }
Devlin Cronin5218d262018-07-09 20:18:05743
Devlin Cronin6b492eb2018-08-01 02:37:09744 if ((permissions_store_mask & kRuntimeGrantedPermissions) != 0) {
745 prefs->RemoveRuntimeGrantedPermissions(extension.id(),
746 prefs_permissions_to_remove);
747 }
Devlin Cronin5218d262018-07-09 20:18:05748
Takashi Toyoshima52f24672019-03-05 05:36:36749 NetworkPermissionsUpdateHelper::UpdatePermissions(
Takashi Toyoshima69579072018-11-19 07:10:50750 browser_context_, REMOVED, &extension, active_permissions_to_remove,
751 std::move(completion_callback));
Devlin Cronin5218d262018-07-09 20:18:05752}
753
[email protected]c333e792012-01-06 16:57:39754} // namespace extensions