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