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 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 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 73 ExternalInstallManager::~ExternalInstallManager() { 74 } 75 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 90 void ExternalInstallManager::RemoveExternalInstallError() { 91 error_.reset(); 92 UpdateExternalExtensionAlert(); 93 } 94 95 bool ExternalInstallManager::HasExternalInstallError() const { 96 return error_.get() != NULL; 97 } 98 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 145 void ExternalInstallManager::AcknowledgeExternalExtension( 146 const std::string& id) { 147 extension_prefs_->AcknowledgeExternalExtension(id); 148 UpdateExternalExtensionAlert(); 149 } 150 151 bool ExternalInstallManager::HasExternalInstallBubbleForTesting() const { 152 return error_.get() && 153 error_->alert_type() == ExternalInstallError::BUBBLE_ALERT; 154 } 155 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 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 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 200 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 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