blob: 81e6ad6682192bc65d6f44200f689622ab1da099 [file] [log] [blame]
[email protected]2894a512014-06-26 19:03:561// Copyright 2014 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/external_install_manager.h"
6
7#include <string>
8
9#include "base/logging.h"
asvitkineaa060312016-09-01 22:44:1310#include "base/metrics/histogram_macros.h"
lazyboy0b9b30f2016-01-05 03:15:3711#include "chrome/app/chrome_command_ids.h"
[email protected]2894a512014-06-26 19:03:5612#include "chrome/browser/chrome_notification_types.h"
[email protected]2894a512014-06-26 19:03:5613#include "chrome/browser/extensions/external_install_error.h"
14#include "chrome/browser/profiles/profile.h"
rdevlin.cronin9d7ae4d2017-01-13 16:38:2315#include "components/version_info/version_info.h"
[email protected]2894a512014-06-26 19:03:5616#include "content/public/browser/notification_details.h"
17#include "content/public/browser/notification_source.h"
[email protected]374ceb6f2014-07-02 19:25:3418#include "extensions/browser/extension_prefs.h"
[email protected]2894a512014-06-26 19:03:5619#include "extensions/browser/extension_registry.h"
[email protected]2894a512014-06-26 19:03:5620#include "extensions/common/extension.h"
[email protected]374ceb6f2014-07-02 19:25:3421#include "extensions/common/feature_switch.h"
rdevlin.cronin9d7ae4d2017-01-13 16:38:2322#include "extensions/common/features/feature_channel.h"
[email protected]374ceb6f2014-07-02 19:25:3423#include "extensions/common/manifest.h"
rockotd5546142014-10-15 00:29:0824#include "extensions/common/manifest_url_handlers.h"
[email protected]2894a512014-06-26 19:03:5625
26namespace extensions {
27
[email protected]374ceb6f2014-07-02 19:25:3428namespace {
29
30// Histogram values for logging events related to externally installed
31// extensions.
32enum ExternalExtensionEvent {
33 EXTERNAL_EXTENSION_INSTALLED = 0,
34 EXTERNAL_EXTENSION_IGNORED,
35 EXTERNAL_EXTENSION_REENABLED,
36 EXTERNAL_EXTENSION_UNINSTALLED,
37 EXTERNAL_EXTENSION_BUCKET_BOUNDARY,
38};
39
[email protected]374ceb6f2014-07-02 19:25:3440// Prompt the user this many times before considering an extension acknowledged.
41const int kMaxExtensionAcknowledgePromptCount = 3;
[email protected]374ceb6f2014-07-02 19:25:3442
43void LogExternalExtensionEvent(const Extension* extension,
44 ExternalExtensionEvent event) {
45 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
46 event,
47 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
48 if (ManifestURL::UpdatesFromGallery(extension)) {
49 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
50 event,
51 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
52 } else {
53 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
54 event,
55 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
56 }
57}
58
59} // namespace
60
[email protected]2894a512014-06-26 19:03:5661ExternalInstallManager::ExternalInstallManager(
[email protected]374ceb6f2014-07-02 19:25:3462 content::BrowserContext* browser_context,
63 bool is_first_run)
64 : browser_context_(browser_context),
65 is_first_run_(is_first_run),
66 extension_prefs_(ExtensionPrefs::Get(browser_context_)),
lazyboy1899eec42016-03-08 19:00:5067 currently_visible_install_alert_(nullptr),
[email protected]374ceb6f2014-07-02 19:25:3468 extension_registry_observer_(this) {
[email protected]2894a512014-06-26 19:03:5669 DCHECK(browser_context_);
70 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
rdevlin.cronina1c3f1a2017-05-18 17:45:4671 Profile* profile = Profile::FromBrowserContext(browser_context_);
72 registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_REMOVED,
73 content::Source<Profile>(profile));
74 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
75 content::Source<Profile>(profile));
rdevlin.cronine1456712016-12-29 22:47:2876 // Populate the set of unacknowledged external extensions now. We can't just
77 // rely on IsUnacknowledgedExternalExtension() for cases like
78 // OnExtensionLoaded(), since we need to examine the disable reasons, which
79 // can be removed throughout the session.
80 for (const auto& extension :
81 ExtensionRegistry::Get(browser_context)->disabled_extensions()) {
82 if (IsUnacknowledgedExternalExtension(*extension))
83 unacknowledged_ids_.insert(extension->id());
84 }
[email protected]2894a512014-06-26 19:03:5685}
86
87ExternalInstallManager::~ExternalInstallManager() {
88}
89
rdevlin.cronin9d7ae4d2017-01-13 16:38:2390
91bool ExternalInstallManager::IsPromptingEnabled() {
rdevlin.cronin9d7ae4d2017-01-13 16:38:2392 return FeatureSwitch::prompt_for_external_extensions()->IsEnabled();
rdevlin.cronin9d7ae4d2017-01-13 16:38:2393}
94
[email protected]2894a512014-06-26 19:03:5695void ExternalInstallManager::AddExternalInstallError(const Extension* extension,
96 bool is_new_profile) {
rdevlin.croninb2daf2e42016-01-14 20:00:5497 // Error already exists or has been previously shown.
skyostil32fa6b3f92016-08-12 13:15:4398 if (base::ContainsKey(errors_, extension->id()) ||
rdevlin.croninb2daf2e42016-01-14 20:00:5499 shown_ids_.count(extension->id()) > 0)
lazyboy0b9b30f2016-01-05 03:15:37100 return;
[email protected]2894a512014-06-26 19:03:56101
[email protected]374ceb6f2014-07-02 19:25:34102 ExternalInstallError::AlertType alert_type =
103 (ManifestURL::UpdatesFromGallery(extension) && !is_new_profile)
104 ? ExternalInstallError::BUBBLE_ALERT
105 : ExternalInstallError::MENU_ALERT;
106
dchengc963c7142016-04-08 03:55:22107 std::unique_ptr<ExternalInstallError> error(new ExternalInstallError(
[email protected]374ceb6f2014-07-02 19:25:34108 browser_context_, extension->id(), alert_type, this));
rdevlin.croninb2daf2e42016-01-14 20:00:54109 shown_ids_.insert(extension->id());
lazyboy0b9b30f2016-01-05 03:15:37110 errors_.insert(std::make_pair(extension->id(), std::move(error)));
[email protected]2894a512014-06-26 19:03:56111}
112
lazyboy0b9b30f2016-01-05 03:15:37113void ExternalInstallManager::RemoveExternalInstallError(
114 const std::string& extension_id) {
rdevlin.cronine1456712016-12-29 22:47:28115 auto iter = errors_.find(extension_id);
lazyboy1899eec42016-03-08 19:00:50116 if (iter != errors_.end()) {
Karan Bhatiae9d5166f2017-07-17 22:06:43117 // The |extension_id| may be owned by the ExternalInstallError, which is
118 // deleted subsequently. To avoid any UAFs, make a safe copy of
119 // |extension_id| now.
120 std::string extension_id_copy = extension_id;
121
lazyboy1899eec42016-03-08 19:00:50122 if (iter->second.get() == currently_visible_install_alert_)
123 currently_visible_install_alert_ = nullptr;
124 errors_.erase(iter);
Devlin Cronin63bd5552018-01-18 19:05:35125 // No need to erase the ID from |unacknowledged_ids_|; it's already in
126 // |shown_ids_|.
rdevlin.croninb2daf2e42016-01-14 20:00:54127 UpdateExternalExtensionAlert();
lazyboy1899eec42016-03-08 19:00:50128 }
[email protected]2894a512014-06-26 19:03:56129}
130
[email protected]374ceb6f2014-07-02 19:25:34131void ExternalInstallManager::UpdateExternalExtensionAlert() {
lazyboy0b9b30f2016-01-05 03:15:37132 // If the feature is not enabled do nothing.
rdevlin.cronin9d7ae4d2017-01-13 16:38:23133 if (!IsPromptingEnabled())
[email protected]374ceb6f2014-07-02 19:25:34134 return;
[email protected]374ceb6f2014-07-02 19:25:34135
136 // Look for any extensions that were disabled because of being unacknowledged
137 // external extensions.
[email protected]374ceb6f2014-07-02 19:25:34138 const ExtensionSet& disabled_extensions =
139 ExtensionRegistry::Get(browser_context_)->disabled_extensions();
Owen Minb71016d2018-01-11 01:51:49140 const ExtensionSet& blocked_extensions =
141 ExtensionRegistry::Get(browser_context_)->blocked_extensions();
Devlin Cronin96a2cf92018-04-18 19:09:59142
143 // The list of ids can be mutated during this loop, so make a copy.
144 const std::set<ExtensionId> ids_copy = unacknowledged_ids_;
145 for (const auto& id : ids_copy) {
rdevlin.cronine1456712016-12-29 22:47:28146 if (base::ContainsKey(errors_, id) || shown_ids_.count(id) > 0)
lazyboy0b9b30f2016-01-05 03:15:37147 continue;
148
Owen Minb71016d2018-01-11 01:51:49149 // Ignore the blocked and disabled extensions. They will be put into
150 // disabled list once unblocked.
151 if (blocked_extensions.GetByID(id))
152 continue;
153
rdevlin.cronine1456712016-12-29 22:47:28154 const Extension* extension = disabled_extensions.GetByID(id);
155 CHECK(extension);
lazyboy0b9b30f2016-01-05 03:15:37156
157 // Warn the user about the suspicious extension.
rdevlin.cronine1456712016-12-29 22:47:28158 if (extension_prefs_->IncrementAcknowledgePromptCount(id) >
lazyboy0b9b30f2016-01-05 03:15:37159 kMaxExtensionAcknowledgePromptCount) {
160 // Stop prompting for this extension and record metrics.
rdevlin.cronine1456712016-12-29 22:47:28161 extension_prefs_->AcknowledgeExternalExtension(id);
162 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_IGNORED);
Devlin Cronin96a2cf92018-04-18 19:09:59163 unacknowledged_ids_.erase(id);
lazyboy0b9b30f2016-01-05 03:15:37164 continue;
[email protected]374ceb6f2014-07-02 19:25:34165 }
lazyboy0b9b30f2016-01-05 03:15:37166
167 if (is_first_run_)
rdevlin.cronine1456712016-12-29 22:47:28168 extension_prefs_->SetExternalInstallFirstRun(id);
lazyboy0b9b30f2016-01-05 03:15:37169
170 // |first_run| is true if the extension was installed during a first run
171 // (even if it's post-first run now).
rdevlin.cronine1456712016-12-29 22:47:28172 AddExternalInstallError(extension,
173 extension_prefs_->IsExternalInstallFirstRun(id));
[email protected]374ceb6f2014-07-02 19:25:34174 }
[email protected]374ceb6f2014-07-02 19:25:34175}
176
177void ExternalInstallManager::AcknowledgeExternalExtension(
178 const std::string& id) {
Devlin Cronin96a2cf92018-04-18 19:09:59179 unacknowledged_ids_.erase(id);
[email protected]374ceb6f2014-07-02 19:25:34180 extension_prefs_->AcknowledgeExternalExtension(id);
181 UpdateExternalExtensionAlert();
182}
183
lazyboy1899eec42016-03-08 19:00:50184void ExternalInstallManager::DidChangeInstallAlertVisibility(
185 ExternalInstallError* external_install_error,
186 bool visible) {
187 if (visible) {
188 currently_visible_install_alert_ = external_install_error;
189 } else if (!visible &&
190 currently_visible_install_alert_ == external_install_error) {
191 currently_visible_install_alert_ = nullptr;
192 }
193}
194
lazyboy0b9b30f2016-01-05 03:15:37195std::vector<ExternalInstallError*>
196ExternalInstallManager::GetErrorsForTesting() {
197 std::vector<ExternalInstallError*> errors;
198 for (auto const& error : errors_)
199 errors.push_back(error.second.get());
200 return errors;
[email protected]2894a512014-06-26 19:03:56201}
202
Devlin Cronin63bd5552018-01-18 19:05:35203void ExternalInstallManager::ClearShownIdsForTesting() {
204 shown_ids_.clear();
205}
206
[email protected]2894a512014-06-26 19:03:56207void ExternalInstallManager::OnExtensionLoaded(
208 content::BrowserContext* browser_context,
209 const Extension* extension) {
rdevlin.cronine1456712016-12-29 22:47:28210 if (!unacknowledged_ids_.count(extension->id()))
[email protected]374ceb6f2014-07-02 19:25:34211 return;
212
213 // We treat loading as acknowledgement (since the user consciously chose to
214 // re-enable the extension).
215 AcknowledgeExternalExtension(extension->id());
216 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_REENABLED);
217
218 // If we had an error for this extension, remove it.
lazyboy0b9b30f2016-01-05 03:15:37219 RemoveExternalInstallError(extension->id());
[email protected]374ceb6f2014-07-02 19:25:34220}
221
222void ExternalInstallManager::OnExtensionInstalled(
223 content::BrowserContext* browser_context,
[email protected]38e872532014-07-16 23:27:51224 const Extension* extension,
225 bool is_update) {
[email protected]374ceb6f2014-07-02 19:25:34226 // Certain extension locations are specific enough that we can
227 // auto-acknowledge any extension that came from one of them.
228 if (Manifest::IsPolicyLocation(extension->location()) ||
229 extension->location() == Manifest::EXTERNAL_COMPONENT) {
230 AcknowledgeExternalExtension(extension->id());
231 return;
[email protected]2894a512014-06-26 19:03:56232 }
[email protected]374ceb6f2014-07-02 19:25:34233
rdevlin.cronine1456712016-12-29 22:47:28234 if (!IsUnacknowledgedExternalExtension(*extension))
[email protected]374ceb6f2014-07-02 19:25:34235 return;
236
rdevlin.cronine1456712016-12-29 22:47:28237 unacknowledged_ids_.insert(extension->id());
[email protected]374ceb6f2014-07-02 19:25:34238 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_INSTALLED);
[email protected]374ceb6f2014-07-02 19:25:34239 UpdateExternalExtensionAlert();
240}
241
242void ExternalInstallManager::OnExtensionUninstalled(
243 content::BrowserContext* browser_context,
[email protected]e43c61f2014-07-20 21:46:34244 const Extension* extension,
245 extensions::UninstallReason reason) {
rdevlin.cronine1456712016-12-29 22:47:28246 if (unacknowledged_ids_.erase(extension->id()))
[email protected]374ceb6f2014-07-02 19:25:34247 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_UNINSTALLED);
248}
249
250bool ExternalInstallManager::IsUnacknowledgedExternalExtension(
rdevlin.cronine1456712016-12-29 22:47:28251 const Extension& extension) const {
rdevlin.cronin9d7ae4d2017-01-13 16:38:23252 if (!IsPromptingEnabled())
[email protected]374ceb6f2014-07-02 19:25:34253 return false;
254
rdevlin.cronine1456712016-12-29 22:47:28255 int disable_reasons = extension_prefs_->GetDisableReasons(extension.id());
256 bool is_from_sideload_wipeout =
Minh X. Nguyen45479012017-08-18 21:35:36257 (disable_reasons & disable_reason::DISABLE_SIDELOAD_WIPEOUT) != 0;
rdevlin.cronine1456712016-12-29 22:47:28258 // We don't consider extensions that weren't disabled for being external so
259 // that we grandfather in extensions. External extensions are only disabled on
260 // install with the "prompt for external extensions" feature enabled.
261 bool is_disabled_external =
Minh X. Nguyen45479012017-08-18 21:35:36262 (disable_reasons & disable_reason::DISABLE_EXTERNAL_EXTENSION) != 0;
rdevlin.cronine1456712016-12-29 22:47:28263 return is_disabled_external && !is_from_sideload_wipeout &&
264 Manifest::IsExternalLocation(extension.location()) &&
265 !extension_prefs_->IsExternalExtensionAcknowledged(extension.id());
[email protected]2894a512014-06-26 19:03:56266}
267
268void ExternalInstallManager::Observe(
269 int type,
270 const content::NotificationSource& source,
271 const content::NotificationDetails& details) {
rdevlin.cronina1c3f1a2017-05-18 17:45:46272 switch (type) {
273 case extensions::NOTIFICATION_EXTENSION_REMOVED: {
274 // The error is invalidated if the extension has been loaded or removed.
275 // It's a shame we have to use the notification system (instead of the
276 // registry observer) for this, but the ExtensionUnloaded notification is
277 // not sent out if the extension is disabled (which it is here).
278 const std::string& extension_id =
279 content::Details<const Extension>(details).ptr()->id();
280 if (base::ContainsKey(errors_, extension_id))
281 RemoveExternalInstallError(extension_id);
282 break;
283 }
284 case chrome::NOTIFICATION_PROFILE_DESTROYED:
285 DCHECK_EQ(Profile::FromBrowserContext(browser_context_),
286 content::Source<const Profile>(source).ptr());
287 // Delete all errors when the profile is shutting down, before associated
288 // services are deleted.
289 errors_.clear();
290 break;
291 default:
292 NOTREACHED();
293 }
[email protected]2894a512014-06-26 19:03:56294}
295
296} // namespace extensions