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
63namespace extensions {
64
65namespace {
66
67const char kTestExtensionName[] = "FooBar";
68const char kTestingUsername[] = "user1@example.com";
69
70}  // namespace
71
72// Helper class for asynchronous waiting.
73class 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
135class 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
187class 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
408TEST_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
432TEST_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
461TEST_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
476TEST_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