OLD | NEW |
(Empty) | |
| 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 "base/message_loop/message_loop_proxy.h" |
| 6 #include "chrome/browser/apps/ephemeral_app_launcher.h" |
| 7 #include "chrome/browser/extensions/extension_install_checker.h" |
| 8 #include "chrome/browser/extensions/extension_service.h" |
| 9 #include "chrome/browser/extensions/extension_test_message_listener.h" |
| 10 #include "chrome/browser/extensions/test_blacklist.h" |
| 11 #include "chrome/browser/extensions/webstore_installer_test.h" |
| 12 #include "chrome/common/chrome_switches.h" |
| 13 #include "content/public/test/test_utils.h" |
| 14 #include "extensions/browser/extension_prefs.h" |
| 15 #include "extensions/browser/extension_registry.h" |
| 16 #include "extensions/browser/extension_system.h" |
| 17 #include "extensions/browser/extension_util.h" |
| 18 #include "extensions/browser/management_policy.h" |
| 19 |
| 20 using extensions::Extension; |
| 21 using extensions::ExtensionPrefs; |
| 22 using extensions::ExtensionRegistry; |
| 23 using extensions::ExtensionSystem; |
| 24 namespace webstore_install = extensions::webstore_install; |
| 25 |
| 26 namespace { |
| 27 |
| 28 const char kWebstoreDomain[] = "cws.com"; |
| 29 const char kAppDomain[] = "app.com"; |
| 30 const char kNonAppDomain[] = "nonapp.com"; |
| 31 const char kTestDataPath[] = "extensions/platform_apps/ephemeral_launcher"; |
| 32 |
| 33 const char kExtensionId[] = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeid"; |
| 34 const char kExtensionTestPath[] = "extension"; |
| 35 const char kNonExistentId[] = "baaaaaaaaaaaaaaaaaaaaaaaaaaaadid"; |
| 36 const char kDefaultAppId[] = "kbiancnbopdghkfedjhfdoegjadfjeal"; |
| 37 const char kDefaultAppCrxFilename[] = "app.crx"; |
| 38 const char kDefaultAppTestPath[] = "app"; |
| 39 const char kAppWithPermissionsId[] = "mbfcnecjknjpipkfkoangpfnhhlpamki"; |
| 40 const char kAppWithPermissionsFilename[] = "app_with_permissions.crx"; |
| 41 |
| 42 class ExtensionInstallCheckerMock : public extensions::ExtensionInstallChecker { |
| 43 public: |
| 44 ExtensionInstallCheckerMock(Profile* profile, |
| 45 const std::string& requirements_error) |
| 46 : extensions::ExtensionInstallChecker(profile), |
| 47 requirements_error_(requirements_error) {} |
| 48 |
| 49 virtual ~ExtensionInstallCheckerMock() {} |
| 50 |
| 51 private: |
| 52 virtual void CheckRequirements() OVERRIDE { |
| 53 // Simulate an asynchronous operation. |
| 54 base::MessageLoopProxy::current()->PostTask( |
| 55 FROM_HERE, |
| 56 base::Bind(&ExtensionInstallCheckerMock::RequirementsErrorCheckDone, |
| 57 base::Unretained(this), |
| 58 current_sequence_number())); |
| 59 } |
| 60 |
| 61 void RequirementsErrorCheckDone(int sequence_number) { |
| 62 std::vector<std::string> errors; |
| 63 errors.push_back(requirements_error_); |
| 64 OnRequirementsCheckDone(sequence_number, errors); |
| 65 } |
| 66 |
| 67 std::string requirements_error_; |
| 68 }; |
| 69 |
| 70 class EphemeralAppLauncherForTest : public EphemeralAppLauncher { |
| 71 public: |
| 72 EphemeralAppLauncherForTest(const std::string& id, Profile* profile) |
| 73 : EphemeralAppLauncher(id, profile, NULL, LaunchCallback()), |
| 74 install_initiated_(false), |
| 75 install_prompt_created_(false) {} |
| 76 |
| 77 bool install_initiated() const { return install_initiated_; } |
| 78 bool install_prompt_created() const { return install_prompt_created_; } |
| 79 |
| 80 void set_requirements_error(const std::string& error) { |
| 81 requirements_check_error_ = error; |
| 82 } |
| 83 |
| 84 private: |
| 85 // Override necessary functions for testing. |
| 86 |
| 87 virtual scoped_ptr<extensions::ExtensionInstallChecker> CreateInstallChecker() |
| 88 OVERRIDE { |
| 89 if (requirements_check_error_.empty()) { |
| 90 return EphemeralAppLauncher::CreateInstallChecker(); |
| 91 } else { |
| 92 return scoped_ptr<extensions::ExtensionInstallChecker>( |
| 93 new ExtensionInstallCheckerMock(profile(), |
| 94 requirements_check_error_)); |
| 95 } |
| 96 } |
| 97 |
| 98 virtual scoped_ptr<ExtensionInstallPrompt> CreateInstallUI() OVERRIDE { |
| 99 install_prompt_created_ = true; |
| 100 return EphemeralAppLauncher::CreateInstallUI(); |
| 101 } |
| 102 |
| 103 virtual scoped_ptr<extensions::WebstoreInstaller::Approval> CreateApproval() |
| 104 const OVERRIDE { |
| 105 install_initiated_ = true; |
| 106 return EphemeralAppLauncher::CreateApproval(); |
| 107 } |
| 108 |
| 109 private: |
| 110 virtual ~EphemeralAppLauncherForTest() {} |
| 111 friend class base::RefCountedThreadSafe<EphemeralAppLauncherForTest>; |
| 112 |
| 113 mutable bool install_initiated_; |
| 114 std::string requirements_check_error_; |
| 115 bool install_prompt_created_; |
| 116 }; |
| 117 |
| 118 class LaunchObserver { |
| 119 public: |
| 120 LaunchObserver() |
| 121 : done_(false), |
| 122 waiting_(false), |
| 123 result_(webstore_install::UNKNOWN_ERROR) {} |
| 124 |
| 125 webstore_install::Result result() const { return result_; } |
| 126 const std::string& error() const { return error_; } |
| 127 |
| 128 void OnLaunchCallback(webstore_install::Result result, |
| 129 const std::string& error) { |
| 130 result_ = result; |
| 131 error_ = error; |
| 132 done_ = true; |
| 133 if (waiting_) { |
| 134 waiting_ = false; |
| 135 base::MessageLoopForUI::current()->Quit(); |
| 136 } |
| 137 } |
| 138 |
| 139 void Wait() { |
| 140 if (done_) |
| 141 return; |
| 142 |
| 143 waiting_ = true; |
| 144 content::RunMessageLoop(); |
| 145 } |
| 146 |
| 147 private: |
| 148 bool done_; |
| 149 bool waiting_; |
| 150 webstore_install::Result result_; |
| 151 std::string error_; |
| 152 }; |
| 153 |
| 154 class ManagementPolicyMock : public extensions::ManagementPolicy::Provider { |
| 155 public: |
| 156 ManagementPolicyMock() {} |
| 157 |
| 158 virtual std::string GetDebugPolicyProviderName() const OVERRIDE { |
| 159 return "ManagementPolicyMock"; |
| 160 } |
| 161 |
| 162 virtual bool UserMayLoad(const Extension* extension, |
| 163 base::string16* error) const OVERRIDE { |
| 164 return false; |
| 165 } |
| 166 }; |
| 167 |
| 168 } // namespace |
| 169 |
| 170 class EphemeralAppLauncherTest : public WebstoreInstallerTest { |
| 171 public: |
| 172 EphemeralAppLauncherTest() |
| 173 : WebstoreInstallerTest(kWebstoreDomain, |
| 174 kTestDataPath, |
| 175 kDefaultAppCrxFilename, |
| 176 kAppDomain, |
| 177 kNonAppDomain) {} |
| 178 |
| 179 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE { |
| 180 WebstoreInstallerTest::SetUpCommandLine(command_line); |
| 181 |
| 182 // Enable ephemeral apps flag. |
| 183 command_line->AppendSwitch(switches::kEnableEphemeralApps); |
| 184 } |
| 185 |
| 186 base::FilePath GetTestPath(const char* test_name) { |
| 187 return test_data_dir_.AppendASCII("platform_apps/ephemeral_launcher") |
| 188 .AppendASCII(test_name); |
| 189 } |
| 190 |
| 191 const Extension* GetInstalledExtension(const std::string& id) { |
| 192 return ExtensionRegistry::Get(profile()) |
| 193 ->GetExtensionById(id, ExtensionRegistry::EVERYTHING); |
| 194 } |
| 195 |
| 196 void SetCrxFilename(const std::string& filename) { |
| 197 GURL crx_url = GenerateTestServerUrl(kWebstoreDomain, filename); |
| 198 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( |
| 199 switches::kAppsGalleryUpdateURL, crx_url.spec()); |
| 200 } |
| 201 |
| 202 void StartLauncherAndCheckResult(EphemeralAppLauncherForTest* launcher, |
| 203 webstore_install::Result expected_result, |
| 204 bool expect_install_initiated) { |
| 205 ExtensionTestMessageListener launched_listener("launched", false); |
| 206 LaunchObserver launch_observer; |
| 207 |
| 208 launcher->launch_callback_ = base::Bind(&LaunchObserver::OnLaunchCallback, |
| 209 base::Unretained(&launch_observer)); |
| 210 launcher->Start(); |
| 211 launch_observer.Wait(); |
| 212 |
| 213 // Verify the launch result. |
| 214 EXPECT_EQ(expected_result, launch_observer.result()); |
| 215 EXPECT_EQ(expect_install_initiated, launcher->install_initiated()); |
| 216 |
| 217 // Verify that the app was actually launched if the launcher succeeded. |
| 218 if (launch_observer.result() == webstore_install::SUCCESS) |
| 219 EXPECT_TRUE(launched_listener.WaitUntilSatisfied()); |
| 220 else |
| 221 EXPECT_FALSE(launched_listener.was_satisfied()); |
| 222 |
| 223 // Check the reference count to ensure the launcher instance will not be |
| 224 // leaked. |
| 225 EXPECT_TRUE(launcher->HasOneRef()); |
| 226 } |
| 227 |
| 228 void RunLaunchTest(const std::string& id, |
| 229 webstore_install::Result expected_result, |
| 230 bool expect_install_initiated) { |
| 231 scoped_refptr<EphemeralAppLauncherForTest> launcher( |
| 232 new EphemeralAppLauncherForTest(id, profile())); |
| 233 StartLauncherAndCheckResult( |
| 234 launcher.get(), expected_result, expect_install_initiated); |
| 235 } |
| 236 |
| 237 void ValidateAppInstalledEphemerally(const std::string& id) { |
| 238 EXPECT_TRUE(GetInstalledExtension(id)); |
| 239 EXPECT_TRUE(extensions::util::IsEphemeralApp(id, profile())); |
| 240 } |
| 241 |
| 242 const Extension* InstallAndDisableApp( |
| 243 const char* test_path, |
| 244 Extension::DisableReason disable_reason) { |
| 245 const Extension* app = InstallExtension(GetTestPath(test_path), 1); |
| 246 EXPECT_TRUE(app); |
| 247 if (!app) |
| 248 return NULL; |
| 249 |
| 250 if (disable_reason == Extension::DISABLE_GREYLIST) { |
| 251 ExtensionPrefs::Get(profile())->SetExtensionBlacklistState( |
| 252 app->id(), extensions::BLACKLISTED_MALWARE); |
| 253 } |
| 254 |
| 255 ExtensionService* service = |
| 256 ExtensionSystem::Get(profile())->extension_service(); |
| 257 service->DisableExtension(app->id(), disable_reason); |
| 258 |
| 259 if (disable_reason == Extension::DISABLE_PERMISSIONS_INCREASE) { |
| 260 // When an extension is disabled due to a permissions increase, this |
| 261 // flag needs to be set too, for some reason. |
| 262 ExtensionPrefs::Get(profile()) |
| 263 ->SetDidExtensionEscalatePermissions(app, true); |
| 264 } |
| 265 |
| 266 EXPECT_FALSE( |
| 267 ExtensionRegistry::Get(profile())->enabled_extensions().Contains( |
| 268 app->id())); |
| 269 return app; |
| 270 } |
| 271 }; |
| 272 |
| 273 class EphemeralAppLauncherTestDisabled : public EphemeralAppLauncherTest { |
| 274 public: |
| 275 virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE { |
| 276 // Skip EphemeralAppLauncherTest as it enables the feature. |
| 277 WebstoreInstallerTest::SetUpCommandLine(command_line); |
| 278 } |
| 279 }; |
| 280 |
| 281 // Verifies that an ephemeral app will not be installed and launched if the |
| 282 // feature is disabled. |
| 283 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTestDisabled, FeatureDisabled) { |
| 284 RunLaunchTest( |
| 285 kDefaultAppCrxFilename, webstore_install::LAUNCH_FEATURE_DISABLED, false); |
| 286 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); |
| 287 } |
| 288 |
| 289 // Verifies that an app with no permission warnings will be installed |
| 290 // ephemerally and launched without prompting the user. |
| 291 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, |
| 292 LaunchAppWithNoPermissionWarnings) { |
| 293 scoped_refptr<EphemeralAppLauncherForTest> launcher( |
| 294 new EphemeralAppLauncherForTest(kDefaultAppId, profile())); |
| 295 StartLauncherAndCheckResult(launcher.get(), webstore_install::SUCCESS, true); |
| 296 ValidateAppInstalledEphemerally(kDefaultAppId); |
| 297 |
| 298 // Apps with no permission warnings should not result in a prompt. |
| 299 EXPECT_FALSE(launcher->install_prompt_created()); |
| 300 |
| 301 // After an app has been installed ephemerally, it can be launched again |
| 302 // without installing from the web store. |
| 303 RunLaunchTest(kDefaultAppId, webstore_install::SUCCESS, false); |
| 304 } |
| 305 |
| 306 // Verifies that an app with permission warnings will be installed |
| 307 // ephemerally and launched if accepted by the user. |
| 308 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, |
| 309 LaunchAppWithPermissionsWarnings) { |
| 310 SetCrxFilename(kAppWithPermissionsFilename); |
| 311 AutoAcceptInstall(); |
| 312 |
| 313 scoped_refptr<EphemeralAppLauncherForTest> launcher( |
| 314 new EphemeralAppLauncherForTest(kAppWithPermissionsId, profile())); |
| 315 StartLauncherAndCheckResult(launcher.get(), webstore_install::SUCCESS, true); |
| 316 ValidateAppInstalledEphemerally(kAppWithPermissionsId); |
| 317 EXPECT_TRUE(launcher->install_prompt_created()); |
| 318 } |
| 319 |
| 320 // Verifies that an app with permission warnings will not be installed |
| 321 // ephemerally if cancelled by the user. |
| 322 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, |
| 323 CancelInstallAppWithPermissionWarnings) { |
| 324 SetCrxFilename(kAppWithPermissionsFilename); |
| 325 AutoCancelInstall(); |
| 326 |
| 327 scoped_refptr<EphemeralAppLauncherForTest> launcher( |
| 328 new EphemeralAppLauncherForTest(kAppWithPermissionsId, profile())); |
| 329 StartLauncherAndCheckResult( |
| 330 launcher.get(), webstore_install::USER_CANCELLED, false); |
| 331 EXPECT_FALSE(GetInstalledExtension(kAppWithPermissionsId)); |
| 332 EXPECT_TRUE(launcher->install_prompt_created()); |
| 333 } |
| 334 |
| 335 // Verifies that an extension will not be installed ephemerally. |
| 336 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, InstallExtension) { |
| 337 RunLaunchTest( |
| 338 kExtensionId, webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, false); |
| 339 EXPECT_FALSE(GetInstalledExtension(kExtensionId)); |
| 340 } |
| 341 |
| 342 // Verifies that an already installed extension will not be launched. |
| 343 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchExtension) { |
| 344 const Extension* extension = |
| 345 InstallExtension(GetTestPath(kExtensionTestPath), 1); |
| 346 ASSERT_TRUE(extension); |
| 347 RunLaunchTest(extension->id(), |
| 348 webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE, |
| 349 false); |
| 350 } |
| 351 |
| 352 // Verifies that the EphemeralAppLauncher handles non-existent extension ids. |
| 353 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, NonExistentExtensionId) { |
| 354 RunLaunchTest( |
| 355 kNonExistentId, webstore_install::WEBSTORE_REQUEST_ERROR, false); |
| 356 EXPECT_FALSE(GetInstalledExtension(kNonExistentId)); |
| 357 } |
| 358 |
| 359 // Verifies that an app blocked by management policy is not installed |
| 360 // ephemerally. |
| 361 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlockedByPolicy) { |
| 362 // Register a provider that blocks the installation of all apps. |
| 363 ManagementPolicyMock policy; |
| 364 ExtensionSystem::Get(profile())->management_policy()->RegisterProvider( |
| 365 &policy); |
| 366 |
| 367 RunLaunchTest(kDefaultAppId, webstore_install::BLOCKED_BY_POLICY, false); |
| 368 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); |
| 369 } |
| 370 |
| 371 // Verifies that an app blacklisted for malware is not installed ephemerally. |
| 372 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlacklistedForMalware) { |
| 373 // Mock a BLACKLISTED_MALWARE return status. |
| 374 extensions::TestBlacklist blacklist_tester( |
| 375 ExtensionSystem::Get(profile())->blacklist()); |
| 376 blacklist_tester.SetBlacklistState( |
| 377 kDefaultAppId, extensions::BLACKLISTED_MALWARE, false); |
| 378 |
| 379 RunLaunchTest(kDefaultAppId, webstore_install::BLACKLISTED, false); |
| 380 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); |
| 381 } |
| 382 |
| 383 // Verifies that an app with unknown blacklist status is installed ephemerally |
| 384 // and launched. |
| 385 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, BlacklistStateUnknown) { |
| 386 // Mock a BLACKLISTED_MALWARE return status. |
| 387 extensions::TestBlacklist blacklist_tester( |
| 388 ExtensionSystem::Get(profile())->blacklist()); |
| 389 blacklist_tester.SetBlacklistState( |
| 390 kDefaultAppId, extensions::BLACKLISTED_UNKNOWN, false); |
| 391 |
| 392 RunLaunchTest(kDefaultAppId, webstore_install::SUCCESS, true); |
| 393 ValidateAppInstalledEphemerally(kDefaultAppId); |
| 394 } |
| 395 |
| 396 // Verifies that an app with unsupported requirements is not installed |
| 397 // ephemerally. |
| 398 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, UnsupportedRequirements) { |
| 399 scoped_refptr<EphemeralAppLauncherForTest> launcher( |
| 400 new EphemeralAppLauncherForTest(kDefaultAppId, profile())); |
| 401 launcher->set_requirements_error("App has unsupported requirements"); |
| 402 |
| 403 StartLauncherAndCheckResult( |
| 404 launcher.get(), webstore_install::REQUIREMENT_VIOLATIONS, false); |
| 405 EXPECT_FALSE(GetInstalledExtension(kDefaultAppId)); |
| 406 } |
| 407 |
| 408 // Verifies that an app disabled due to permissions increase can be enabled |
| 409 // and launched. |
| 410 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, EnableAndLaunchApp) { |
| 411 const Extension* app = InstallAndDisableApp( |
| 412 kDefaultAppTestPath, Extension::DISABLE_PERMISSIONS_INCREASE); |
| 413 ASSERT_TRUE(app); |
| 414 |
| 415 AutoAcceptInstall(); |
| 416 RunLaunchTest(app->id(), webstore_install::SUCCESS, false); |
| 417 } |
| 418 |
| 419 // Verifies that if the user cancels the enable flow, the app will not be |
| 420 // enabled and launched. |
| 421 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, EnableCancelled) { |
| 422 const Extension* app = InstallAndDisableApp( |
| 423 kDefaultAppTestPath, Extension::DISABLE_PERMISSIONS_INCREASE); |
| 424 ASSERT_TRUE(app); |
| 425 |
| 426 AutoCancelInstall(); |
| 427 RunLaunchTest(app->id(), webstore_install::USER_CANCELLED, false); |
| 428 } |
| 429 |
| 430 // Verifies that an installed app that had been blocked by policy cannot be |
| 431 // launched. |
| 432 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchAppBlockedByPolicy) { |
| 433 const Extension* app = InstallExtension(GetTestPath(kDefaultAppTestPath), 1); |
| 434 ASSERT_TRUE(app); |
| 435 |
| 436 // Simulate blocking of the app after it has been installed. |
| 437 ManagementPolicyMock policy; |
| 438 ExtensionSystem::Get(profile())->management_policy()->RegisterProvider( |
| 439 &policy); |
| 440 ExtensionSystem::Get(profile())->extension_service()->CheckManagementPolicy(); |
| 441 |
| 442 RunLaunchTest(app->id(), webstore_install::BLOCKED_BY_POLICY, false); |
| 443 } |
| 444 |
| 445 // Verifies that an installed blacklisted app cannot be launched. |
| 446 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, LaunchBlacklistedApp) { |
| 447 const Extension* app = |
| 448 InstallAndDisableApp(kDefaultAppTestPath, Extension::DISABLE_GREYLIST); |
| 449 ASSERT_TRUE(app); |
| 450 |
| 451 RunLaunchTest(app->id(), webstore_install::BLACKLISTED, false); |
| 452 } |
| 453 |
| 454 // Verifies that an installed app with unsupported requirements cannot be |
| 455 // launched. |
| 456 IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, |
| 457 LaunchAppWithUnsupportedRequirements) { |
| 458 const Extension* app = InstallAndDisableApp( |
| 459 kDefaultAppTestPath, Extension::DISABLE_UNSUPPORTED_REQUIREMENT); |
| 460 ASSERT_TRUE(app); |
| 461 |
| 462 RunLaunchTest(app->id(), webstore_install::REQUIREMENT_VIOLATIONS, false); |
| 463 } |
OLD | NEW |