1 // 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"
10 #include "base/metrics/histogram.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/extensions/external_install_error.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/common/extensions/manifest_url_handler.h"
15 #include "content/public/browser/notification_details.h"
16 #include "content/public/browser/notification_source.h"
17 #include "extensions/browser/extension_prefs.h"
18 #include "extensions/browser/extension_registry.h"
19 #include "extensions/common/extension.h"
20 #include "extensions/common/feature_switch.h"
21 #include "extensions/common/manifest.h"
22
23 namespace extensions {
24
25 namespace {
26
27 // Histogram values for logging events related to externally installed
28 // extensions.
29 enum ExternalExtensionEvent {
30 EXTERNAL_EXTENSION_INSTALLED = 0,
31 EXTERNAL_EXTENSION_IGNORED,
32 EXTERNAL_EXTENSION_REENABLED,
33 EXTERNAL_EXTENSION_UNINSTALLED,
34 EXTERNAL_EXTENSION_BUCKET_BOUNDARY,
35 };
36
37 // Prompt the user this many times before considering an extension acknowledged.
38 const int kMaxExtensionAcknowledgePromptCount = 3;
39
LogExternalExtensionEvent(const Extension * extension,ExternalExtensionEvent event)40 void LogExternalExtensionEvent(const Extension* extension,
41 ExternalExtensionEvent event) {
42 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
43 event,
44 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
45 if (ManifestURL::UpdatesFromGallery(extension)) {
46 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
47 event,
48 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
49 } else {
50 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
51 event,
52 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
53 }
54 }
55
56 } // namespace
57
ExternalInstallManager(content::BrowserContext * browser_context,bool is_first_run)58 ExternalInstallManager::ExternalInstallManager(
59 content::BrowserContext* browser_context,
60 bool is_first_run)
61 : browser_context_(browser_context),
62 is_first_run_(is_first_run),
63 extension_prefs_(ExtensionPrefs::Get(browser_context_)),
64 extension_registry_observer_(this) {
65 DCHECK(browser_context_);
66 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
67 registrar_.Add(
68 this,
69 extensions::NOTIFICATION_EXTENSION_REMOVED,
70 content::Source<Profile>(Profile::FromBrowserContext(browser_context_)));
71 }
72
~ExternalInstallManager()73 ExternalInstallManager::~ExternalInstallManager() {
74 }
75
AddExternalInstallError(const Extension * extension,bool is_new_profile)76 void ExternalInstallManager::AddExternalInstallError(const Extension* extension,
77 bool is_new_profile) {
78 if (HasExternalInstallError())
79 return; // Only have one external install error at a time.
80
81 ExternalInstallError::AlertType alert_type =
82 (ManifestURL::UpdatesFromGallery(extension) && !is_new_profile)
83 ? ExternalInstallError::BUBBLE_ALERT
84 : ExternalInstallError::MENU_ALERT;
85
86 error_.reset(new ExternalInstallError(
87 browser_context_, extension->id(), alert_type, this));
88 }
89
RemoveExternalInstallError()90 void ExternalInstallManager::RemoveExternalInstallError() {
91 error_.reset();
92 UpdateExternalExtensionAlert();
93 }
94
HasExternalInstallError() const95 bool ExternalInstallManager::HasExternalInstallError() const {
96 return error_.get() != NULL;
97 }
98
UpdateExternalExtensionAlert()99 void ExternalInstallManager::UpdateExternalExtensionAlert() {
100 // If the feature is not enabled, or there is already an error displayed, do
101 // nothing.
102 if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled() ||
103 HasExternalInstallError()) {
104 return;
105 }
106
107 // Look for any extensions that were disabled because of being unacknowledged
108 // external extensions.
109 const Extension* extension = NULL;
110 const ExtensionSet& disabled_extensions =
111 ExtensionRegistry::Get(browser_context_)->disabled_extensions();
112 for (ExtensionSet::const_iterator iter = disabled_extensions.begin();
113 iter != disabled_extensions.end();
114 ++iter) {
115 if (IsUnacknowledgedExternalExtension(iter->get())) {
116 extension = iter->get();
117 break;
118 }
119 }
120
121 if (!extension)
122 return; // No unacknowledged external extensions.
123
124 // Otherwise, warn the user about the suspicious extension.
125 if (extension_prefs_->IncrementAcknowledgePromptCount(extension->id()) >
126 kMaxExtensionAcknowledgePromptCount) {
127 // Stop prompting for this extension and record metrics.
128 extension_prefs_->AcknowledgeExternalExtension(extension->id());
129 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_IGNORED);
130
131 // Check if there's another extension that needs prompting.
132 UpdateExternalExtensionAlert();
133 return;
134 }
135
136 if (is_first_run_)
137 extension_prefs_->SetExternalInstallFirstRun(extension->id());
138
139 // |first_run| is true if the extension was installed during a first run
140 // (even if it's post-first run now).
141 AddExternalInstallError(
142 extension, extension_prefs_->IsExternalInstallFirstRun(extension->id()));
143 }
144
AcknowledgeExternalExtension(const std::string & id)145 void ExternalInstallManager::AcknowledgeExternalExtension(
146 const std::string& id) {
147 extension_prefs_->AcknowledgeExternalExtension(id);
148 UpdateExternalExtensionAlert();
149 }
150
HasExternalInstallBubbleForTesting() const151 bool ExternalInstallManager::HasExternalInstallBubbleForTesting() const {
152 return error_.get() &&
153 error_->alert_type() == ExternalInstallError::BUBBLE_ALERT;
154 }
155
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)156 void ExternalInstallManager::OnExtensionLoaded(
157 content::BrowserContext* browser_context,
158 const Extension* extension) {
159 if (!IsUnacknowledgedExternalExtension(extension))
160 return;
161
162 // We treat loading as acknowledgement (since the user consciously chose to
163 // re-enable the extension).
164 AcknowledgeExternalExtension(extension->id());
165 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_REENABLED);
166
167 // If we had an error for this extension, remove it.
168 if (error_.get() && extension->id() == error_->extension_id())
169 RemoveExternalInstallError();
170 }
171
OnExtensionInstalled(content::BrowserContext * browser_context,const Extension * extension,bool is_update)172 void ExternalInstallManager::OnExtensionInstalled(
173 content::BrowserContext* browser_context,
174 const Extension* extension,
175 bool is_update) {
176 // Certain extension locations are specific enough that we can
177 // auto-acknowledge any extension that came from one of them.
178 if (Manifest::IsPolicyLocation(extension->location()) ||
179 extension->location() == Manifest::EXTERNAL_COMPONENT) {
180 AcknowledgeExternalExtension(extension->id());
181 return;
182 }
183
184 if (!IsUnacknowledgedExternalExtension(extension))
185 return;
186
187 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_INSTALLED);
188
189 UpdateExternalExtensionAlert();
190 }
191
OnExtensionUninstalled(content::BrowserContext * browser_context,const Extension * extension,extensions::UninstallReason reason)192 void ExternalInstallManager::OnExtensionUninstalled(
193 content::BrowserContext* browser_context,
194 const Extension* extension,
195 extensions::UninstallReason reason) {
196 if (IsUnacknowledgedExternalExtension(extension))
197 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_UNINSTALLED);
198 }
199
IsUnacknowledgedExternalExtension(const Extension * extension) const200 bool ExternalInstallManager::IsUnacknowledgedExternalExtension(
201 const Extension* extension) const {
202 if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled())
203 return false;
204
205 return (Manifest::IsExternalLocation(extension->location()) &&
206 !extension_prefs_->IsExternalExtensionAcknowledged(extension->id()) &&
207 !(extension_prefs_->GetDisableReasons(extension->id()) &
208 Extension::DISABLE_SIDELOAD_WIPEOUT));
209 }
210
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)211 void ExternalInstallManager::Observe(
212 int type,
213 const content::NotificationSource& source,
214 const content::NotificationDetails& details) {
215 DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_REMOVED, type);
216 // The error is invalidated if the extension has been loaded or removed.
217 // It's a shame we have to use the notification system (instead of the
218 // registry observer) for this, but the ExtensionUnloaded notification is
219 // not sent out if the extension is disabled (which it is here).
220 if (error_.get() &&
221 content::Details<const Extension>(details).ptr()->id() ==
222 error_->extension_id()) {
223 RemoveExternalInstallError();
224 }
225 }
226
227 } // namespace extensions
228