blob: 38e20553657f5daec3a86db1a0ccbe0cbe00032c [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/common/extension.h"
[email protected]374ceb6f2014-07-02 19:25:3420#include "extensions/common/feature_switch.h"
rdevlin.cronin9d7ae4d2017-01-13 16:38:2321#include "extensions/common/features/feature_channel.h"
[email protected]374ceb6f2014-07-02 19:25:3422#include "extensions/common/manifest.h"
rockotd5546142014-10-15 00:29:0823#include "extensions/common/manifest_url_handlers.h"
[email protected]2894a512014-06-26 19:03:5624
25namespace extensions {
26
[email protected]374ceb6f2014-07-02 19:25:3427namespace {
28
29// Histogram values for logging events related to externally installed
30// extensions.
31enum ExternalExtensionEvent {
32 EXTERNAL_EXTENSION_INSTALLED = 0,
33 EXTERNAL_EXTENSION_IGNORED,
34 EXTERNAL_EXTENSION_REENABLED,
35 EXTERNAL_EXTENSION_UNINSTALLED,
36 EXTERNAL_EXTENSION_BUCKET_BOUNDARY,
37};
38
[email protected]374ceb6f2014-07-02 19:25:3439// Prompt the user this many times before considering an extension acknowledged.
40const int kMaxExtensionAcknowledgePromptCount = 3;
[email protected]374ceb6f2014-07-02 19:25:3441
42void LogExternalExtensionEvent(const Extension* extension,
43 ExternalExtensionEvent event) {
44 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
45 event,
46 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
47 if (ManifestURL::UpdatesFromGallery(extension)) {
48 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
49 event,
50 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
51 } else {
52 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
53 event,
54 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
55 }
56}
57
58} // namespace
59
[email protected]2894a512014-06-26 19:03:5660ExternalInstallManager::ExternalInstallManager(
[email protected]374ceb6f2014-07-02 19:25:3461 content::BrowserContext* browser_context,
62 bool is_first_run)
63 : browser_context_(browser_context),
64 is_first_run_(is_first_run),
65 extension_prefs_(ExtensionPrefs::Get(browser_context_)),
Evan Stade75872a62019-09-06 21:17:3866 currently_visible_install_alert_(nullptr) {
[email protected]2894a512014-06-26 19:03:5667 DCHECK(browser_context_);
68 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
rdevlin.cronina1c3f1a2017-05-18 17:45:4669 Profile* profile = Profile::FromBrowserContext(browser_context_);
70 registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_REMOVED,
71 content::Source<Profile>(profile));
rdevlin.cronine1456712016-12-29 22:47:2872 // Populate the set of unacknowledged external extensions now. We can't just
73 // rely on IsUnacknowledgedExternalExtension() for cases like
74 // OnExtensionLoaded(), since we need to examine the disable reasons, which
75 // can be removed throughout the session.
76 for (const auto& extension :
77 ExtensionRegistry::Get(browser_context)->disabled_extensions()) {
78 if (IsUnacknowledgedExternalExtension(*extension))
79 unacknowledged_ids_.insert(extension->id());
80 }
[email protected]2894a512014-06-26 19:03:5681}
82
83ExternalInstallManager::~ExternalInstallManager() {
Evan Stadeadf14bb2019-10-03 19:13:0684 // Shutdown should have been called.
85 DCHECK(errors_.empty());
[email protected]2894a512014-06-26 19:03:5686}
87
Evan Stadeadf14bb2019-10-03 19:13:0688void ExternalInstallManager::Shutdown() {
89 // Delete all errors when the profile is shutting down, before associated
90 // services are deleted.
91 errors_.clear();
92}
rdevlin.cronin9d7ae4d2017-01-13 16:38:2393
94bool ExternalInstallManager::IsPromptingEnabled() {
rdevlin.cronin9d7ae4d2017-01-13 16:38:2395 return FeatureSwitch::prompt_for_external_extensions()->IsEnabled();
rdevlin.cronin9d7ae4d2017-01-13 16:38:2396}
97
[email protected]2894a512014-06-26 19:03:5698void ExternalInstallManager::AddExternalInstallError(const Extension* extension,
99 bool is_new_profile) {
rdevlin.croninb2daf2e42016-01-14 20:00:54100 // Error already exists or has been previously shown.
Jan Wilken Dörrieade79222019-06-06 19:01:12101 if (base::Contains(errors_, extension->id()) ||
rdevlin.croninb2daf2e42016-01-14 20:00:54102 shown_ids_.count(extension->id()) > 0)
lazyboy0b9b30f2016-01-05 03:15:37103 return;
[email protected]2894a512014-06-26 19:03:56104
[email protected]374ceb6f2014-07-02 19:25:34105 ExternalInstallError::AlertType alert_type =
106 (ManifestURL::UpdatesFromGallery(extension) && !is_new_profile)
107 ? ExternalInstallError::BUBBLE_ALERT
108 : ExternalInstallError::MENU_ALERT;
109
dchengc963c7142016-04-08 03:55:22110 std::unique_ptr<ExternalInstallError> error(new ExternalInstallError(
[email protected]374ceb6f2014-07-02 19:25:34111 browser_context_, extension->id(), alert_type, this));
rdevlin.croninb2daf2e42016-01-14 20:00:54112 shown_ids_.insert(extension->id());
lazyboy0b9b30f2016-01-05 03:15:37113 errors_.insert(std::make_pair(extension->id(), std::move(error)));
[email protected]2894a512014-06-26 19:03:56114}
115
lazyboy0b9b30f2016-01-05 03:15:37116void ExternalInstallManager::RemoveExternalInstallError(
117 const std::string& extension_id) {
rdevlin.cronine1456712016-12-29 22:47:28118 auto iter = errors_.find(extension_id);
lazyboy1899eec42016-03-08 19:00:50119 if (iter != errors_.end()) {
Karan Bhatiae9d5166f2017-07-17 22:06:43120 // The |extension_id| may be owned by the ExternalInstallError, which is
121 // deleted subsequently. To avoid any UAFs, make a safe copy of
122 // |extension_id| now.
123 std::string extension_id_copy = extension_id;
124
lazyboy1899eec42016-03-08 19:00:50125 if (iter->second.get() == currently_visible_install_alert_)
126 currently_visible_install_alert_ = nullptr;
127 errors_.erase(iter);
Devlin Cronin63bd5552018-01-18 19:05:35128 // No need to erase the ID from |unacknowledged_ids_|; it's already in
129 // |shown_ids_|.
rdevlin.croninb2daf2e42016-01-14 20:00:54130 UpdateExternalExtensionAlert();
lazyboy1899eec42016-03-08 19:00:50131 }
[email protected]2894a512014-06-26 19:03:56132}
133
[email protected]374ceb6f2014-07-02 19:25:34134void ExternalInstallManager::UpdateExternalExtensionAlert() {
lazyboy0b9b30f2016-01-05 03:15:37135 // If the feature is not enabled do nothing.
rdevlin.cronin9d7ae4d2017-01-13 16:38:23136 if (!IsPromptingEnabled())
[email protected]374ceb6f2014-07-02 19:25:34137 return;
[email protected]374ceb6f2014-07-02 19:25:34138
139 // Look for any extensions that were disabled because of being unacknowledged
140 // external extensions.
[email protected]374ceb6f2014-07-02 19:25:34141 const ExtensionSet& disabled_extensions =
142 ExtensionRegistry::Get(browser_context_)->disabled_extensions();
Owen Minb71016d2018-01-11 01:51:49143 const ExtensionSet& blocked_extensions =
144 ExtensionRegistry::Get(browser_context_)->blocked_extensions();
Devlin Cronin96a2cf92018-04-18 19:09:59145
146 // The list of ids can be mutated during this loop, so make a copy.
147 const std::set<ExtensionId> ids_copy = unacknowledged_ids_;
148 for (const auto& id : ids_copy) {
Jan Wilken Dörrieade79222019-06-06 19:01:12149 if (base::Contains(errors_, id) || shown_ids_.count(id) > 0)
lazyboy0b9b30f2016-01-05 03:15:37150 continue;
151
Owen Minb71016d2018-01-11 01:51:49152 // Ignore the blocked and disabled extensions. They will be put into
153 // disabled list once unblocked.
154 if (blocked_extensions.GetByID(id))
155 continue;
156
rdevlin.cronine1456712016-12-29 22:47:28157 const Extension* extension = disabled_extensions.GetByID(id);
158 CHECK(extension);
lazyboy0b9b30f2016-01-05 03:15:37159
160 // Warn the user about the suspicious extension.
rdevlin.cronine1456712016-12-29 22:47:28161 if (extension_prefs_->IncrementAcknowledgePromptCount(id) >
lazyboy0b9b30f2016-01-05 03:15:37162 kMaxExtensionAcknowledgePromptCount) {
163 // Stop prompting for this extension and record metrics.
rdevlin.cronine1456712016-12-29 22:47:28164 extension_prefs_->AcknowledgeExternalExtension(id);
165 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_IGNORED);
Devlin Cronin96a2cf92018-04-18 19:09:59166 unacknowledged_ids_.erase(id);
lazyboy0b9b30f2016-01-05 03:15:37167 continue;
[email protected]374ceb6f2014-07-02 19:25:34168 }
lazyboy0b9b30f2016-01-05 03:15:37169
170 if (is_first_run_)
rdevlin.cronine1456712016-12-29 22:47:28171 extension_prefs_->SetExternalInstallFirstRun(id);
lazyboy0b9b30f2016-01-05 03:15:37172
173 // |first_run| is true if the extension was installed during a first run
174 // (even if it's post-first run now).
rdevlin.cronine1456712016-12-29 22:47:28175 AddExternalInstallError(extension,
176 extension_prefs_->IsExternalInstallFirstRun(id));
[email protected]374ceb6f2014-07-02 19:25:34177 }
[email protected]374ceb6f2014-07-02 19:25:34178}
179
180void ExternalInstallManager::AcknowledgeExternalExtension(
181 const std::string& id) {
Devlin Cronin96a2cf92018-04-18 19:09:59182 unacknowledged_ids_.erase(id);
[email protected]374ceb6f2014-07-02 19:25:34183 extension_prefs_->AcknowledgeExternalExtension(id);
184 UpdateExternalExtensionAlert();
185}
186
lazyboy1899eec42016-03-08 19:00:50187void ExternalInstallManager::DidChangeInstallAlertVisibility(
188 ExternalInstallError* external_install_error,
189 bool visible) {
190 if (visible) {
191 currently_visible_install_alert_ = external_install_error;
192 } else if (!visible &&
193 currently_visible_install_alert_ == external_install_error) {
194 currently_visible_install_alert_ = nullptr;
195 }
196}
197
lazyboy0b9b30f2016-01-05 03:15:37198std::vector<ExternalInstallError*>
199ExternalInstallManager::GetErrorsForTesting() {
200 std::vector<ExternalInstallError*> errors;
201 for (auto const& error : errors_)
202 errors.push_back(error.second.get());
203 return errors;
[email protected]2894a512014-06-26 19:03:56204}
205
Devlin Cronin63bd5552018-01-18 19:05:35206void ExternalInstallManager::ClearShownIdsForTesting() {
207 shown_ids_.clear();
208}
209
[email protected]2894a512014-06-26 19:03:56210void ExternalInstallManager::OnExtensionLoaded(
211 content::BrowserContext* browser_context,
212 const Extension* extension) {
rdevlin.cronine1456712016-12-29 22:47:28213 if (!unacknowledged_ids_.count(extension->id()))
[email protected]374ceb6f2014-07-02 19:25:34214 return;
215
216 // We treat loading as acknowledgement (since the user consciously chose to
217 // re-enable the extension).
218 AcknowledgeExternalExtension(extension->id());
219 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_REENABLED);
220
221 // If we had an error for this extension, remove it.
lazyboy0b9b30f2016-01-05 03:15:37222 RemoveExternalInstallError(extension->id());
[email protected]374ceb6f2014-07-02 19:25:34223}
224
225void ExternalInstallManager::OnExtensionInstalled(
226 content::BrowserContext* browser_context,
[email protected]38e872532014-07-16 23:27:51227 const Extension* extension,
228 bool is_update) {
[email protected]374ceb6f2014-07-02 19:25:34229 // Certain extension locations are specific enough that we can
230 // auto-acknowledge any extension that came from one of them.
231 if (Manifest::IsPolicyLocation(extension->location()) ||
232 extension->location() == Manifest::EXTERNAL_COMPONENT) {
233 AcknowledgeExternalExtension(extension->id());
234 return;
[email protected]2894a512014-06-26 19:03:56235 }
[email protected]374ceb6f2014-07-02 19:25:34236
rdevlin.cronine1456712016-12-29 22:47:28237 if (!IsUnacknowledgedExternalExtension(*extension))
[email protected]374ceb6f2014-07-02 19:25:34238 return;
239
rdevlin.cronine1456712016-12-29 22:47:28240 unacknowledged_ids_.insert(extension->id());
[email protected]374ceb6f2014-07-02 19:25:34241 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_INSTALLED);
[email protected]374ceb6f2014-07-02 19:25:34242 UpdateExternalExtensionAlert();
243}
244
245void ExternalInstallManager::OnExtensionUninstalled(
246 content::BrowserContext* browser_context,
[email protected]e43c61f2014-07-20 21:46:34247 const Extension* extension,
248 extensions::UninstallReason reason) {
rdevlin.cronine1456712016-12-29 22:47:28249 if (unacknowledged_ids_.erase(extension->id()))
[email protected]374ceb6f2014-07-02 19:25:34250 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_UNINSTALLED);
251}
252
253bool ExternalInstallManager::IsUnacknowledgedExternalExtension(
rdevlin.cronine1456712016-12-29 22:47:28254 const Extension& extension) const {
rdevlin.cronin9d7ae4d2017-01-13 16:38:23255 if (!IsPromptingEnabled())
[email protected]374ceb6f2014-07-02 19:25:34256 return false;
257
rdevlin.cronine1456712016-12-29 22:47:28258 int disable_reasons = extension_prefs_->GetDisableReasons(extension.id());
259 bool is_from_sideload_wipeout =
Minh X. Nguyen45479012017-08-18 21:35:36260 (disable_reasons & disable_reason::DISABLE_SIDELOAD_WIPEOUT) != 0;
rdevlin.cronine1456712016-12-29 22:47:28261 // We don't consider extensions that weren't disabled for being external so
262 // that we grandfather in extensions. External extensions are only disabled on
263 // install with the "prompt for external extensions" feature enabled.
264 bool is_disabled_external =
Minh X. Nguyen45479012017-08-18 21:35:36265 (disable_reasons & disable_reason::DISABLE_EXTERNAL_EXTENSION) != 0;
rdevlin.cronine1456712016-12-29 22:47:28266 return is_disabled_external && !is_from_sideload_wipeout &&
267 Manifest::IsExternalLocation(extension.location()) &&
268 !extension_prefs_->IsExternalExtensionAcknowledged(extension.id());
[email protected]2894a512014-06-26 19:03:56269}
270
271void ExternalInstallManager::Observe(
272 int type,
273 const content::NotificationSource& source,
274 const content::NotificationDetails& details) {
Evan Stadeadf14bb2019-10-03 19:13:06275 DCHECK_EQ(type, extensions::NOTIFICATION_EXTENSION_REMOVED);
276 // The error is invalidated if the extension has been loaded or removed.
277 // It's a shame we have to use the notification system (instead of the
278 // registry observer) for this, but the ExtensionUnloaded notification is
279 // not sent out if the extension is disabled (which it is here).
280 const std::string& extension_id =
281 content::Details<const Extension>(details).ptr()->id();
282 if (base::Contains(errors_, extension_id))
283 RemoveExternalInstallError(extension_id);
[email protected]2894a512014-06-26 19:03:56284}
285
286} // namespace extensions