1 // 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 "base/memory/ref_counted.h" 6 #include "chrome/browser/download/download_crx_util.h" 7 #include "chrome/browser/extensions/crx_installer.h" 8 #include "chrome/browser/extensions/extension_browsertest.h" 9 #include "chrome/browser/extensions/extension_install_prompt.h" 10 #include "chrome/browser/extensions/extension_service.h" 11 #include "chrome/browser/extensions/extension_system.h" 12 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/ui/browser.h" 14 #include "chrome/browser/ui/browser_window.h" 15 #include "chrome/browser/ui/tabs/tab_strip_model.h" 16 #include "chrome/common/extensions/extension.h" 17 #include "chrome/common/extensions/extension_file_util.h" 18 #include "chrome/common/extensions/feature_switch.h" 19 #include "chrome/common/extensions/permissions/permission_set.h" 20 #include "chrome/test/base/ui_test_utils.h" 21 #include "content/public/browser/download_manager.h" 22 #include "content/public/test/download_test_observer.h" 23 #include "extensions/common/switches.h" 24 #include "grit/generated_resources.h" 25 #include "ui/base/l10n/l10n_util.h" 26 27 class SkBitmap; 28 29 namespace extensions { 30 31 namespace { 32 33 class MockInstallPrompt; 34 35 // This class holds information about things that happen with a 36 // MockInstallPrompt. We create the MockInstallPrompt but need to pass 37 // ownership of it to CrxInstaller, so it isn't safe to hang this data on 38 // MockInstallPrompt itself becuase we can't guarantee it's lifetime. 39 class MockPromptProxy : 40 public base::RefCountedThreadSafe<MockPromptProxy> { 41 public: 42 explicit MockPromptProxy(content::WebContents* web_contents); 43 44 bool did_succeed() const { return !extension_id_.empty(); } 45 const std::string& extension_id() { return extension_id_; } 46 bool confirmation_requested() const { return confirmation_requested_; } 47 const string16& error() const { return error_; } 48 49 // To have any effect, this should be called before CreatePrompt. 50 void set_record_oauth2_grant(bool record_oauth2_grant) { 51 record_oauth2_grant_.reset(new bool(record_oauth2_grant)); 52 } 53 54 void set_extension_id(const std::string& id) { extension_id_ = id; } 55 void set_confirmation_requested() { confirmation_requested_ = true; } 56 void set_error(const string16& error) { error_ = error; } 57 58 scoped_ptr<ExtensionInstallPrompt> CreatePrompt(); 59 60 private: 61 friend class base::RefCountedThreadSafe<MockPromptProxy>; 62 virtual ~MockPromptProxy(); 63 64 // Data used to create a prompt. 65 content::WebContents* web_contents_; 66 scoped_ptr<bool> record_oauth2_grant_; 67 68 // Data reported back to us by the prompt we created. 69 bool confirmation_requested_; 70 std::string extension_id_; 71 string16 error_; 72 }; 73 74 class MockInstallPrompt : public ExtensionInstallPrompt { 75 public: 76 MockInstallPrompt(content::WebContents* web_contents, 77 MockPromptProxy* proxy) : 78 ExtensionInstallPrompt(web_contents), 79 proxy_(proxy) {} 80 81 void set_record_oauth2_grant(bool record) { record_oauth2_grant_ = record; } 82 83 // Overriding some of the ExtensionInstallUI API. 84 virtual void ConfirmInstall( 85 Delegate* delegate, 86 const Extension* extension, 87 const ShowDialogCallback& show_dialog_callback) OVERRIDE { 88 proxy_->set_confirmation_requested(); 89 delegate->InstallUIProceed(); 90 } 91 virtual void OnInstallSuccess(const Extension* extension, 92 SkBitmap* icon) OVERRIDE { 93 proxy_->set_extension_id(extension->id()); 94 base::MessageLoopForUI::current()->Quit(); 95 } 96 virtual void OnInstallFailure(const CrxInstallerError& error) OVERRIDE { 97 proxy_->set_error(error.message()); 98 base::MessageLoopForUI::current()->Quit(); 99 } 100 101 private: 102 scoped_refptr<MockPromptProxy> proxy_; 103 }; 104 105 106 MockPromptProxy::MockPromptProxy(content::WebContents* web_contents) : 107 web_contents_(web_contents), 108 confirmation_requested_(false) { 109 } 110 111 MockPromptProxy::~MockPromptProxy() {} 112 113 scoped_ptr<ExtensionInstallPrompt> MockPromptProxy::CreatePrompt() { 114 scoped_ptr<MockInstallPrompt> prompt( 115 new MockInstallPrompt(web_contents_, this)); 116 if (record_oauth2_grant_.get()) 117 prompt->set_record_oauth2_grant(*record_oauth2_grant_.get()); 118 return prompt.PassAs<ExtensionInstallPrompt>(); 119 } 120 121 122 scoped_refptr<MockPromptProxy> CreateMockPromptProxyForBrowser( 123 Browser* browser) { 124 return new MockPromptProxy( 125 browser->tab_strip_model()->GetActiveWebContents()); 126 } 127 128 } // namespace 129 130 class ExtensionCrxInstallerTest : public ExtensionBrowserTest { 131 public: 132 // Installs a crx from |crx_relpath| (a path relative to the extension test 133 // data dir) with expected id |id|. Returns the installer. 134 scoped_refptr<CrxInstaller> InstallWithPrompt( 135 const std::string& ext_relpath, 136 const std::string& id, 137 scoped_refptr<MockPromptProxy> mock_install_prompt) { 138 ExtensionService* service = extensions::ExtensionSystem::Get( 139 browser()->profile())->extension_service(); 140 base::FilePath ext_path = test_data_dir_.AppendASCII(ext_relpath); 141 142 std::string error; 143 base::DictionaryValue* parsed_manifest = 144 extension_file_util::LoadManifest(ext_path, &error); 145 if (!parsed_manifest) 146 return scoped_refptr<CrxInstaller>(); 147 148 scoped_ptr<WebstoreInstaller::Approval> approval; 149 if (!id.empty()) { 150 approval = WebstoreInstaller::Approval::CreateWithNoInstallPrompt( 151 browser()->profile(), 152 id, 153 scoped_ptr<base::DictionaryValue>(parsed_manifest)); 154 } 155 156 scoped_refptr<CrxInstaller> installer( 157 CrxInstaller::Create(service, 158 mock_install_prompt->CreatePrompt(), 159 approval.get() /* keep ownership */)); 160 installer->set_allow_silent_install(true); 161 installer->set_is_gallery_install(true); 162 installer->InstallCrx(PackExtension(ext_path)); 163 content::RunMessageLoop(); 164 165 EXPECT_TRUE(mock_install_prompt->did_succeed()); 166 return installer; 167 } 168 169 // Installs an extension and checks that it has scopes granted IFF 170 // |record_oauth2_grant| is true. 171 void CheckHasEmptyScopesAfterInstall(const std::string& ext_relpath, 172 bool record_oauth2_grant) { 173 CommandLine::ForCurrentProcess()->AppendSwitch( 174 switches::kEnableExperimentalExtensionApis); 175 176 ExtensionService* service = extensions::ExtensionSystem::Get( 177 browser()->profile())->extension_service(); 178 179 scoped_refptr<MockPromptProxy> mock_prompt = 180 CreateMockPromptProxyForBrowser(browser()); 181 182 mock_prompt->set_record_oauth2_grant(record_oauth2_grant); 183 scoped_refptr<CrxInstaller> installer = 184 InstallWithPrompt("browsertest/scopes", std::string(), mock_prompt); 185 186 scoped_refptr<PermissionSet> permissions = 187 service->extension_prefs()->GetGrantedPermissions( 188 mock_prompt->extension_id()); 189 ASSERT_TRUE(permissions.get()); 190 } 191 192 // Creates and returns a popup ExtensionHost for an extension and waits 193 // for a url to load in the host's web contents. 194 // The caller is responsible for cleaning up the returned ExtensionHost. 195 ExtensionHost* OpenUrlInExtensionPopupHost(const Extension* extension, 196 const GURL& url) { 197 ExtensionSystem* extension_system = extensions::ExtensionSystem::Get( 198 browser()->profile()); 199 ExtensionProcessManager* epm = extension_system->process_manager(); 200 ExtensionHost* extension_host = 201 epm->CreatePopupHost(extension, url, browser()); 202 203 extension_host->CreateRenderViewSoon(); 204 if (!extension_host->IsRenderViewLive()) { 205 content::WindowedNotificationObserver observer( 206 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME, 207 content::Source<content::WebContents>( 208 extension_host->host_contents())); 209 observer.Wait(); 210 } 211 212 return extension_host; 213 } 214 }; 215 216 #if defined(OS_CHROMEOS) 217 #define MAYBE_Whitelisting DISABLED_Whitelisting 218 #else 219 #define MAYBE_Whitelisting Whitelisting 220 #endif 221 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, MAYBE_Whitelisting) { 222 std::string id = "hdgllgikmikobbofgnabhfimcfoopgnd"; 223 ExtensionService* service = extensions::ExtensionSystem::Get( 224 browser()->profile())->extension_service(); 225 226 // Even whitelisted extensions with NPAPI should not prompt. 227 scoped_refptr<MockPromptProxy> mock_prompt = 228 CreateMockPromptProxyForBrowser(browser()); 229 scoped_refptr<CrxInstaller> installer = 230 InstallWithPrompt("uitest/plugins", id, mock_prompt); 231 EXPECT_FALSE(mock_prompt->confirmation_requested()); 232 EXPECT_TRUE(service->GetExtensionById(id, false)); 233 } 234 235 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, 236 GalleryInstallGetsExperimental) { 237 // We must modify the command line temporarily in order to pack an extension 238 // that requests the experimental permission. 239 CommandLine* command_line = CommandLine::ForCurrentProcess(); 240 CommandLine old_command_line = *command_line; 241 command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis); 242 base::FilePath crx_path = PackExtension( 243 test_data_dir_.AppendASCII("experimental")); 244 ASSERT_FALSE(crx_path.empty()); 245 246 // Now reset the command line so that we are testing specifically whether 247 // installing from webstore enables experimental permissions. 248 *(CommandLine::ForCurrentProcess()) = old_command_line; 249 250 EXPECT_FALSE(InstallExtension(crx_path, 0)); 251 EXPECT_TRUE(InstallExtensionFromWebstore(crx_path, 1)); 252 } 253 254 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, PlatformAppCrx) { 255 CommandLine::ForCurrentProcess()->AppendSwitch( 256 switches::kEnableExperimentalExtensionApis); 257 EXPECT_TRUE(InstallExtension( 258 test_data_dir_.AppendASCII("minimal_platform_app.crx"), 1)); 259 } 260 261 // http://crbug.com/136397 262 #if defined(OS_CHROMEOS) 263 #define MAYBE_PackAndInstallExtension DISABLED_PackAndInstallExtension 264 #else 265 #define MAYBE_PackAndInstallExtension PackAndInstallExtension 266 #endif 267 IN_PROC_BROWSER_TEST_F( 268 ExtensionCrxInstallerTest, MAYBE_PackAndInstallExtension) { 269 if (!FeatureSwitch::easy_off_store_install()->IsEnabled()) 270 return; 271 272 const int kNumDownloadsExpected = 1; 273 274 LOG(ERROR) << "PackAndInstallExtension: Packing extension"; 275 base::FilePath crx_path = PackExtension( 276 test_data_dir_.AppendASCII("common/background_page")); 277 ASSERT_FALSE(crx_path.empty()); 278 std::string crx_path_string(crx_path.value().begin(), crx_path.value().end()); 279 GURL url = GURL(std::string("file:///").append(crx_path_string)); 280 281 scoped_refptr<MockPromptProxy> mock_prompt = 282 CreateMockPromptProxyForBrowser(browser()); 283 download_crx_util::SetMockInstallPromptForTesting( 284 mock_prompt->CreatePrompt()); 285 286 LOG(ERROR) << "PackAndInstallExtension: Getting download manager"; 287 content::DownloadManager* download_manager = 288 content::BrowserContext::GetDownloadManager(browser()->profile()); 289 290 LOG(ERROR) << "PackAndInstallExtension: Setting observer"; 291 scoped_ptr<content::DownloadTestObserver> observer( 292 new content::DownloadTestObserverTerminal( 293 download_manager, kNumDownloadsExpected, 294 content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT)); 295 LOG(ERROR) << "PackAndInstallExtension: Navigating to URL"; 296 ui_test_utils::NavigateToURLWithDisposition(browser(), url, CURRENT_TAB, 297 ui_test_utils::BROWSER_TEST_NONE); 298 299 EXPECT_TRUE(WaitForCrxInstallerDone()); 300 LOG(ERROR) << "PackAndInstallExtension: Extension install"; 301 EXPECT_TRUE(mock_prompt->confirmation_requested()); 302 LOG(ERROR) << "PackAndInstallExtension: Extension install confirmed"; 303 } 304 305 // Tests that scopes are only granted if |record_oauth2_grant_| on the prompt is 306 // true. 307 #if defined(OS_WIN) 308 #define MAYBE_GrantScopes DISABLED_GrantScopes 309 #else 310 #define MAYBE_GrantScopes GrantScopes 311 #endif 312 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, MAYBE_GrantScopes) { 313 EXPECT_NO_FATAL_FAILURE(CheckHasEmptyScopesAfterInstall("browsertest/scopes", 314 true)); 315 } 316 317 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, DoNotGrantScopes) { 318 EXPECT_NO_FATAL_FAILURE(CheckHasEmptyScopesAfterInstall("browsertest/scopes", 319 false)); 320 } 321 322 // Off-store install cannot yet be disabled on Aura. 323 #if defined(USE_AURA) 324 #define MAYBE_AllowOffStore DISABLED_AllowOffStore 325 #else 326 #define MAYBE_AllowOffStore AllowOffStore 327 #endif 328 // Crashy: http://crbug.com/140893 329 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, DISABLED_AllowOffStore) { 330 ExtensionService* service = extensions::ExtensionSystem::Get( 331 browser()->profile())->extension_service(); 332 const bool kTestData[] = {false, true}; 333 334 for (size_t i = 0; i < arraysize(kTestData); ++i) { 335 scoped_refptr<MockPromptProxy> mock_prompt = 336 CreateMockPromptProxyForBrowser(browser()); 337 338 scoped_refptr<CrxInstaller> crx_installer( 339 CrxInstaller::Create(service, mock_prompt->CreatePrompt())); 340 crx_installer->set_install_cause( 341 extension_misc::INSTALL_CAUSE_USER_DOWNLOAD); 342 343 if (kTestData[i]) { 344 crx_installer->set_off_store_install_allow_reason( 345 CrxInstaller::OffStoreInstallAllowedInTest); 346 } 347 348 crx_installer->InstallCrx(test_data_dir_.AppendASCII("good.crx")); 349 EXPECT_EQ(kTestData[i], WaitForExtensionInstall()) << kTestData[i]; 350 EXPECT_EQ(kTestData[i], mock_prompt->did_succeed()); 351 EXPECT_EQ(kTestData[i], mock_prompt->confirmation_requested()) << 352 kTestData[i]; 353 if (kTestData[i]) { 354 EXPECT_EQ(string16(), mock_prompt->error()) << kTestData[i]; 355 } else { 356 EXPECT_EQ(l10n_util::GetStringUTF16( 357 IDS_EXTENSION_INSTALL_DISALLOWED_ON_SITE), 358 mock_prompt->error()) << kTestData[i]; 359 } 360 } 361 } 362 363 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, HiDpiThemeTest) { 364 base::FilePath crx_path = test_data_dir_.AppendASCII("theme_hidpi_crx"); 365 crx_path = crx_path.AppendASCII("theme_hidpi.crx"); 366 367 ASSERT_TRUE(InstallExtension(crx_path,1)); 368 369 const std::string extension_id("gllekhaobjnhgeagipipnkpmmmpchacm"); 370 ExtensionService* service = extensions::ExtensionSystem::Get( 371 browser()->profile())->extension_service(); 372 ASSERT_TRUE(service); 373 const extensions::Extension* extension = 374 service->GetExtensionById(extension_id, false); 375 ASSERT_TRUE(extension); 376 EXPECT_EQ(extension_id, extension->id()); 377 378 UninstallExtension(extension_id); 379 EXPECT_FALSE(service->GetExtensionById(extension_id, false)); 380 } 381 382 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, 383 InstallDelayedUntilNextUpdate) { 384 const std::string extension_id("ldnnhddmnhbkjipkidpdiheffobcpfmf"); 385 base::FilePath crx_path = test_data_dir_.AppendASCII("delayed_install"); 386 ExtensionSystem* extension_system = extensions::ExtensionSystem::Get( 387 browser()->profile()); 388 ExtensionService* service = extension_system->extension_service(); 389 ASSERT_TRUE(service); 390 391 // Install version 1 of the test extension. This extension does not have 392 // a background page but does have a browser action. 393 ASSERT_TRUE(InstallExtension(crx_path.AppendASCII("v1.crx"), 1)); 394 const extensions::Extension* extension = 395 service->GetExtensionById(extension_id, false); 396 ASSERT_TRUE(extension); 397 ASSERT_EQ(extension_id, extension->id()); 398 ASSERT_EQ("1.0", extension->version()->GetString()); 399 400 // Make test extension non-idle by opening the extension's browser action 401 // popup. This should cause the installation to be delayed. 402 std::string popup_url = std::string("chrome-extension://") 403 + extension_id + std::string("/popup.html"); 404 scoped_ptr<ExtensionHost> extension_host = scoped_ptr<ExtensionHost>( 405 OpenUrlInExtensionPopupHost(extension, GURL(popup_url))); 406 407 // Install version 2 of the extension and check that it is indeed delayed. 408 ASSERT_TRUE(UpdateExtensionWaitForIdle( 409 extension_id, crx_path.AppendASCII("v2.crx"), 0)); 410 411 ASSERT_EQ(1u, service->delayed_installs()->size()); 412 extension = service->GetExtensionById(extension_id, false); 413 ASSERT_EQ("1.0", extension->version()->GetString()); 414 415 // Make the extension idle again by navigating away from the extension's 416 // browser action page. This should not trigger the delayed install. 417 extension_system->process_manager()->UnregisterRenderViewHost( 418 extension_host->render_view_host()); 419 ASSERT_EQ(1u, service->delayed_installs()->size()); 420 421 // Install version 3 of the extension. Because the extension is idle, 422 // this install should succeed. 423 ASSERT_TRUE(UpdateExtensionWaitForIdle( 424 extension_id, crx_path.AppendASCII("v3.crx"), 0)); 425 extension = service->GetExtensionById(extension_id, false); 426 ASSERT_EQ("3.0", extension->version()->GetString()); 427 428 // The version 2 delayed install should be cleaned up, and finishing 429 // delayed extension installation shouldn't break anything. 430 ASSERT_EQ(0u, service->delayed_installs()->size()); 431 service->MaybeFinishDelayedInstallations(); 432 extension = service->GetExtensionById(extension_id, false); 433 ASSERT_EQ("3.0", extension->version()->GetString()); 434 } 435 436 IN_PROC_BROWSER_TEST_F(ExtensionCrxInstallerTest, Blacklist) { 437 extensions::Blacklist* blacklist = 438 ExtensionSystem::Get(profile())->blacklist(); 439 440 // Fake the blacklisting of the extension we're about to install by 441 // pretending that we get a blacklist update which includes it. 442 const std::string kId = "gllekhaobjnhgeagipipnkpmmmpchacm"; 443 blacklist->SetFromUpdater(std::vector<std::string>(1, kId), "some-version"); 444 445 base::FilePath crx_path = test_data_dir_.AppendASCII("theme_hidpi_crx") 446 .AppendASCII("theme_hidpi.crx"); 447 EXPECT_FALSE(InstallExtension(crx_path, 0)); 448 } 449 450 } // namespace extensions 451