Home | History | Annotate | Download | only in extensions
      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