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/extension_gcm_app_handler.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/bind_helpers.h" 11 #include "base/command_line.h" 12 #include "base/file_util.h" 13 #include "base/files/file_path.h" 14 #include "base/files/scoped_temp_dir.h" 15 #include "base/location.h" 16 #include "base/logging.h" 17 #include "base/memory/ref_counted.h" 18 #include "base/message_loop/message_loop.h" 19 #include "base/path_service.h" 20 #include "base/prefs/pref_service.h" 21 #include "base/run_loop.h" 22 #include "base/values.h" 23 #include "chrome/browser/chrome_notification_types.h" 24 #include "chrome/browser/extensions/extension_service.h" 25 #include "chrome/browser/extensions/test_extension_service.h" 26 #include "chrome/browser/extensions/test_extension_system.h" 27 #include "chrome/browser/profiles/profile.h" 28 #include "chrome/browser/services/gcm/fake_signin_manager.h" 29 #include "chrome/browser/services/gcm/gcm_profile_service.h" 30 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h" 31 #include "chrome/browser/signin/signin_manager_factory.h" 32 #include "chrome/common/chrome_paths.h" 33 #include "chrome/common/pref_names.h" 34 #include "chrome/test/base/testing_profile.h" 35 #include "components/gcm_driver/fake_gcm_app_handler.h" 36 #include "components/gcm_driver/fake_gcm_client.h" 37 #include "components/gcm_driver/fake_gcm_client_factory.h" 38 #include "components/gcm_driver/gcm_client_factory.h" 39 #include "components/gcm_driver/gcm_driver.h" 40 #include "components/keyed_service/core/keyed_service.h" 41 #include "content/public/browser/browser_context.h" 42 #include "content/public/browser/browser_thread.h" 43 #include "content/public/test/test_browser_thread_bundle.h" 44 #include "content/public/test/test_utils.h" 45 #include "extensions/browser/extension_system.h" 46 #include "extensions/common/extension.h" 47 #include "extensions/common/manifest.h" 48 #include "extensions/common/manifest_constants.h" 49 #include "extensions/common/permissions/api_permission.h" 50 #include "extensions/common/permissions/permissions_data.h" 51 #include "testing/gtest/include/gtest/gtest.h" 52 53 #if !defined(OS_ANDROID) 54 #include "chrome/browser/extensions/api/gcm/gcm_api.h" 55 #endif 56 57 #if defined(OS_CHROMEOS) 58 #include "chrome/browser/chromeos/login/users/user_manager.h" 59 #include "chrome/browser/chromeos/settings/cros_settings.h" 60 #include "chrome/browser/chromeos/settings/device_settings_service.h" 61 #endif 62 63 namespace extensions { 64 65 namespace { 66 67 const char kTestExtensionName[] = "FooBar"; 68 const char kTestingUsername[] = "user1 (at) example.com"; 69 70 } // namespace 71 72 // Helper class for asynchronous waiting. 73 class Waiter { 74 public: 75 Waiter() {} 76 ~Waiter() {} 77 78 // Waits until the asynchronous operation finishes. 79 void WaitUntilCompleted() { 80 run_loop_.reset(new base::RunLoop); 81 run_loop_->Run(); 82 } 83 84 // Signals that the asynchronous operation finishes. 85 void SignalCompleted() { 86 if (run_loop_ && run_loop_->running()) 87 run_loop_->Quit(); 88 } 89 90 // Runs until UI loop becomes idle. 91 void PumpUILoop() { 92 base::MessageLoop::current()->RunUntilIdle(); 93 } 94 95 // Runs until IO loop becomes idle. 96 void PumpIOLoop() { 97 content::BrowserThread::PostTask( 98 content::BrowserThread::IO, 99 FROM_HERE, 100 base::Bind(&Waiter::OnIOLoopPump, base::Unretained(this))); 101 102 WaitUntilCompleted(); 103 } 104 105 private: 106 void PumpIOLoopCompleted() { 107 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 108 109 SignalCompleted(); 110 } 111 112 void OnIOLoopPump() { 113 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 114 115 content::BrowserThread::PostTask( 116 content::BrowserThread::IO, 117 FROM_HERE, 118 base::Bind(&Waiter::OnIOLoopPumpCompleted, base::Unretained(this))); 119 } 120 121 void OnIOLoopPumpCompleted() { 122 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 123 124 content::BrowserThread::PostTask( 125 content::BrowserThread::UI, 126 FROM_HERE, 127 base::Bind(&Waiter::PumpIOLoopCompleted, base::Unretained(this))); 128 } 129 130 scoped_ptr<base::RunLoop> run_loop_; 131 132 DISALLOW_COPY_AND_ASSIGN(Waiter); 133 }; 134 135 class FakeExtensionGCMAppHandler : public ExtensionGCMAppHandler { 136 public: 137 FakeExtensionGCMAppHandler(Profile* profile, Waiter* waiter) 138 : ExtensionGCMAppHandler(profile), 139 waiter_(waiter), 140 unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR), 141 app_handler_count_drop_to_zero_(false) { 142 } 143 144 virtual ~FakeExtensionGCMAppHandler() { 145 } 146 147 virtual void OnMessage( 148 const std::string& app_id, 149 const gcm::GCMClient::IncomingMessage& message) OVERRIDE { 150 } 151 152 virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE { 153 } 154 155 virtual void OnSendError( 156 const std::string& app_id, 157 const gcm::GCMClient::SendErrorDetails& send_error_details) OVERRIDE { 158 } 159 160 virtual void OnUnregisterCompleted(const std::string& app_id, 161 gcm::GCMClient::Result result) OVERRIDE { 162 unregistration_result_ = result; 163 waiter_->SignalCompleted(); 164 } 165 166 virtual void RemoveAppHandler(const std::string& app_id) OVERRIDE{ 167 ExtensionGCMAppHandler::RemoveAppHandler(app_id); 168 if (!GetGCMDriver()->app_handlers().size()) 169 app_handler_count_drop_to_zero_ = true; 170 } 171 172 gcm::GCMClient::Result unregistration_result() const { 173 return unregistration_result_; 174 } 175 bool app_handler_count_drop_to_zero() const { 176 return app_handler_count_drop_to_zero_; 177 } 178 179 private: 180 Waiter* waiter_; 181 gcm::GCMClient::Result unregistration_result_; 182 bool app_handler_count_drop_to_zero_; 183 184 DISALLOW_COPY_AND_ASSIGN(FakeExtensionGCMAppHandler); 185 }; 186 187 class ExtensionGCMAppHandlerTest : public testing::Test { 188 public: 189 static KeyedService* BuildGCMProfileService( 190 content::BrowserContext* context) { 191 return new gcm::GCMProfileService( 192 Profile::FromBrowserContext(context), 193 scoped_ptr<gcm::GCMClientFactory>(new gcm::FakeGCMClientFactory( 194 gcm::FakeGCMClient::NO_DELAY_START, 195 content::BrowserThread::GetMessageLoopProxyForThread( 196 content::BrowserThread::UI), 197 content::BrowserThread::GetMessageLoopProxyForThread( 198 content::BrowserThread::IO)))); 199 } 200 201 ExtensionGCMAppHandlerTest() 202 : extension_service_(NULL), 203 registration_result_(gcm::GCMClient::UNKNOWN_ERROR), 204 unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR) { 205 } 206 207 virtual ~ExtensionGCMAppHandlerTest() { 208 } 209 210 // Overridden from test::Test: 211 virtual void SetUp() OVERRIDE { 212 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 213 214 // Make BrowserThread work in unittest. 215 thread_bundle_.reset(new content::TestBrowserThreadBundle( 216 content::TestBrowserThreadBundle::REAL_IO_THREAD)); 217 218 // Allow extension update to unpack crx in process. 219 in_process_utility_thread_helper_.reset( 220 new content::InProcessUtilityThreadHelper); 221 222 // This is needed to create extension service under CrOS. 223 #if defined(OS_CHROMEOS) 224 test_user_manager_.reset(new chromeos::ScopedTestUserManager()); 225 #endif 226 227 // Create a new profile. 228 TestingProfile::Builder builder; 229 builder.AddTestingFactory(SigninManagerFactory::GetInstance(), 230 gcm::FakeSigninManager::Build); 231 profile_ = builder.Build(); 232 signin_manager_ = static_cast<gcm::FakeSigninManager*>( 233 SigninManagerFactory::GetInstance()->GetForProfile(profile_.get())); 234 235 // Create extension service in order to uninstall the extension. 236 TestExtensionSystem* extension_system( 237 static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()))); 238 base::FilePath extensions_install_dir = 239 temp_dir_.path().Append(FILE_PATH_LITERAL("Extensions")); 240 extension_system->CreateExtensionService( 241 CommandLine::ForCurrentProcess(), extensions_install_dir, false); 242 extension_service_ = extension_system->Get(profile())->extension_service(); 243 extension_service_->set_extensions_enabled(true); 244 extension_service_->set_show_extensions_prompts(false); 245 extension_service_->set_install_updates_when_idle_for_test(false); 246 247 // Enable GCM such that tests could be run on all channels. 248 profile()->GetPrefs()->SetBoolean(prefs::kGCMChannelEnabled, true); 249 250 // Create GCMProfileService that talks with fake GCMClient. 251 gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactoryAndUse( 252 profile(), &ExtensionGCMAppHandlerTest::BuildGCMProfileService); 253 254 // Create a fake version of ExtensionGCMAppHandler. 255 gcm_app_handler_.reset(new FakeExtensionGCMAppHandler(profile(), &waiter_)); 256 } 257 258 virtual void TearDown() OVERRIDE { 259 #if defined(OS_CHROMEOS) 260 test_user_manager_.reset(); 261 #endif 262 263 waiter_.PumpUILoop(); 264 } 265 266 // Returns a barebones test extension. 267 scoped_refptr<Extension> CreateExtension() { 268 base::DictionaryValue manifest; 269 manifest.SetString(manifest_keys::kVersion, "1.0.0.0"); 270 manifest.SetString(manifest_keys::kName, kTestExtensionName); 271 base::ListValue* permission_list = new base::ListValue; 272 permission_list->Append(base::Value::CreateStringValue("gcm")); 273 manifest.Set(manifest_keys::kPermissions, permission_list); 274 275 std::string error; 276 scoped_refptr<Extension> extension = Extension::Create( 277 temp_dir_.path(), 278 Manifest::UNPACKED, 279 manifest, 280 Extension::NO_FLAGS, 281 "ldnnhddmnhbkjipkidpdiheffobcpfmf", 282 &error); 283 EXPECT_TRUE(extension.get()) << error; 284 EXPECT_TRUE( 285 extension->permissions_data()->HasAPIPermission(APIPermission::kGcm)); 286 287 return extension; 288 } 289 290 void LoadExtension(const Extension* extension) { 291 extension_service_->AddExtension(extension); 292 } 293 294 static bool IsCrxInstallerDone(extensions::CrxInstaller** installer, 295 const content::NotificationSource& source, 296 const content::NotificationDetails& details) { 297 return content::Source<extensions::CrxInstaller>(source).ptr() == 298 *installer; 299 } 300 301 void UpdateExtension(const Extension* extension, 302 const std::string& update_crx) { 303 base::FilePath data_dir; 304 if (!PathService::Get(chrome::DIR_TEST_DATA, &data_dir)) { 305 ADD_FAILURE(); 306 return; 307 } 308 data_dir = data_dir.AppendASCII("extensions"); 309 data_dir = data_dir.AppendASCII(update_crx); 310 311 base::FilePath path = temp_dir_.path(); 312 path = path.Append(data_dir.BaseName()); 313 ASSERT_TRUE(base::CopyFile(data_dir, path)); 314 315 extensions::CrxInstaller* installer = NULL; 316 content::WindowedNotificationObserver observer( 317 chrome::NOTIFICATION_CRX_INSTALLER_DONE, 318 base::Bind(&IsCrxInstallerDone, &installer)); 319 extension_service_->UpdateExtension( 320 extension->id(), path, true, &installer); 321 322 if (installer) 323 observer.Wait(); 324 } 325 326 void DisableExtension(const Extension* extension) { 327 extension_service_->DisableExtension( 328 extension->id(), Extension::DISABLE_USER_ACTION); 329 } 330 331 void EnableExtension(const Extension* extension) { 332 extension_service_->EnableExtension(extension->id()); 333 } 334 335 void UninstallExtension(const Extension* extension) { 336 extension_service_->UninstallExtension(extension->id(), false, NULL); 337 } 338 339 void SignIn(const std::string& username) { 340 signin_manager_->SignIn(username); 341 waiter_.PumpIOLoop(); 342 } 343 344 void SignOut() { 345 signin_manager_->SignOut(signin_metrics::SIGNOUT_TEST); 346 waiter_.PumpIOLoop(); 347 } 348 349 void Register(const std::string& app_id, 350 const std::vector<std::string>& sender_ids) { 351 GetGCMDriver()->Register( 352 app_id, 353 sender_ids, 354 base::Bind(&ExtensionGCMAppHandlerTest::RegisterCompleted, 355 base::Unretained(this))); 356 } 357 358 void RegisterCompleted(const std::string& registration_id, 359 gcm::GCMClient::Result result) { 360 registration_result_ = result; 361 waiter_.SignalCompleted(); 362 } 363 364 gcm::GCMDriver* GetGCMDriver() const { 365 return gcm::GCMProfileServiceFactory::GetForProfile(profile())->driver(); 366 } 367 368 bool HasAppHandlers(const std::string& app_id) const { 369 return GetGCMDriver()->app_handlers().count(app_id); 370 } 371 372 Profile* profile() const { return profile_.get(); } 373 Waiter* waiter() { return &waiter_; } 374 FakeExtensionGCMAppHandler* gcm_app_handler() const { 375 return gcm_app_handler_.get(); 376 } 377 gcm::GCMClient::Result registration_result() const { 378 return registration_result_; 379 } 380 gcm::GCMClient::Result unregistration_result() const { 381 return unregistration_result_; 382 } 383 384 private: 385 scoped_ptr<content::TestBrowserThreadBundle> thread_bundle_; 386 scoped_ptr<content::InProcessUtilityThreadHelper> 387 in_process_utility_thread_helper_; 388 scoped_ptr<TestingProfile> profile_; 389 ExtensionService* extension_service_; // Not owned. 390 gcm::FakeSigninManager* signin_manager_; // Not owned. 391 base::ScopedTempDir temp_dir_; 392 393 // This is needed to create extension service under CrOS. 394 #if defined(OS_CHROMEOS) 395 chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; 396 chromeos::ScopedTestCrosSettings test_cros_settings_; 397 scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_; 398 #endif 399 400 Waiter waiter_; 401 scoped_ptr<FakeExtensionGCMAppHandler> gcm_app_handler_; 402 gcm::GCMClient::Result registration_result_; 403 gcm::GCMClient::Result unregistration_result_; 404 405 DISALLOW_COPY_AND_ASSIGN(ExtensionGCMAppHandlerTest); 406 }; 407 408 TEST_F(ExtensionGCMAppHandlerTest, AddAndRemoveAppHandler) { 409 scoped_refptr<Extension> extension(CreateExtension()); 410 411 // App handler is added when extension is loaded. 412 LoadExtension(extension); 413 waiter()->PumpUILoop(); 414 EXPECT_TRUE(HasAppHandlers(extension->id())); 415 416 // App handler is removed when extension is unloaded. 417 DisableExtension(extension); 418 waiter()->PumpUILoop(); 419 EXPECT_FALSE(HasAppHandlers(extension->id())); 420 421 // App handler is added when extension is reloaded. 422 EnableExtension(extension); 423 waiter()->PumpUILoop(); 424 EXPECT_TRUE(HasAppHandlers(extension->id())); 425 426 // App handler is removed when extension is uninstalled. 427 UninstallExtension(extension); 428 waiter()->PumpUILoop(); 429 EXPECT_FALSE(HasAppHandlers(extension->id())); 430 } 431 432 TEST_F(ExtensionGCMAppHandlerTest, UnregisterOnExtensionUninstall) { 433 scoped_refptr<Extension> extension(CreateExtension()); 434 LoadExtension(extension); 435 436 // Sign-in is needed for registration. 437 SignIn(kTestingUsername); 438 439 // Kick off registration. 440 std::vector<std::string> sender_ids; 441 sender_ids.push_back("sender1"); 442 Register(extension->id(), sender_ids); 443 waiter()->WaitUntilCompleted(); 444 EXPECT_EQ(gcm::GCMClient::SUCCESS, registration_result()); 445 446 // Add another app handler in order to prevent the GCM service from being 447 // stopped when the extension is uninstalled. This is needed because otherwise 448 // we are not able to receive the unregistration result. 449 GetGCMDriver()->AddAppHandler("Foo", gcm_app_handler()); 450 451 // Unregistration should be triggered when the extension is uninstalled. 452 UninstallExtension(extension); 453 waiter()->WaitUntilCompleted(); 454 EXPECT_EQ(gcm::GCMClient::SUCCESS, 455 gcm_app_handler()->unregistration_result()); 456 457 // Clean up. 458 GetGCMDriver()->RemoveAppHandler("Foo"); 459 } 460 461 TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionKept) { 462 scoped_refptr<Extension> extension(CreateExtension()); 463 464 // App handler is added when the extension is loaded. 465 LoadExtension(extension); 466 waiter()->PumpUILoop(); 467 EXPECT_TRUE(HasAppHandlers(extension->id())); 468 469 // App handler count should not drop to zero when the extension is updated. 470 UpdateExtension(extension, "gcm2.crx"); 471 waiter()->PumpUILoop(); 472 EXPECT_FALSE(gcm_app_handler()->app_handler_count_drop_to_zero()); 473 EXPECT_TRUE(HasAppHandlers(extension->id())); 474 } 475 476 TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionRemoved) { 477 scoped_refptr<Extension> extension(CreateExtension()); 478 479 // App handler is added when the extension is loaded. 480 LoadExtension(extension); 481 waiter()->PumpUILoop(); 482 EXPECT_TRUE(HasAppHandlers(extension->id())); 483 484 // App handler is removed when the extension is updated to the version that 485 // has GCM permission removed. 486 UpdateExtension(extension, "good2.crx"); 487 waiter()->PumpUILoop(); 488 EXPECT_TRUE(gcm_app_handler()->app_handler_count_drop_to_zero()); 489 EXPECT_FALSE(HasAppHandlers(extension->id())); 490 } 491 492 } // namespace extensions 493