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 <memory>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/check_op.h"
14 #include "base/command_line.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/files/scoped_temp_dir.h"
18 #include "base/location.h"
19 #include "base/macros.h"
20 #include "base/memory/ptr_util.h"
21 #include "base/memory/ref_counted.h"
22 #include "base/path_service.h"
23 #include "base/run_loop.h"
24 #include "base/sequenced_task_runner.h"
25 #include "base/task/thread_pool.h"
26 #include "base/values.h"
27 #include "build/build_config.h"
28 #include "chrome/browser/chrome_notification_types.h"
29 #include "chrome/browser/extensions/api/gcm/gcm_api.h"
30 #include "chrome/browser/extensions/extension_service.h"
31 #include "chrome/browser/extensions/test_extension_service.h"
32 #include "chrome/browser/extensions/test_extension_system.h"
33 #include "chrome/browser/gcm/gcm_product_util.h"
34 #include "chrome/browser/gcm/gcm_profile_service_factory.h"
35 #include "chrome/browser/profiles/profile.h"
36 #include "chrome/browser/signin/identity_manager_factory.h"
37 #include "chrome/common/channel_info.h"
38 #include "chrome/common/chrome_paths.h"
39 #include "chrome/test/base/testing_profile.h"
40 #include "components/gcm_driver/fake_gcm_app_handler.h"
41 #include "components/gcm_driver/fake_gcm_client.h"
42 #include "components/gcm_driver/fake_gcm_client_factory.h"
43 #include "components/gcm_driver/gcm_client_factory.h"
44 #include "components/gcm_driver/gcm_driver.h"
45 #include "components/gcm_driver/gcm_profile_service.h"
46 #include "components/keyed_service/core/keyed_service.h"
47 #include "content/public/browser/browser_context.h"
48 #include "content/public/browser/browser_task_traits.h"
49 #include "content/public/browser/browser_thread.h"
50 #include "content/public/browser/storage_partition.h"
51 #include "content/public/test/browser_task_environment.h"
52 #include "content/public/test/test_utils.h"
53 #include "extensions/browser/extension_system.h"
54 #include "extensions/browser/uninstall_reason.h"
55 #include "extensions/common/extension.h"
56 #include "extensions/common/extension_builder.h"
57 #include "extensions/common/manifest.h"
58 #include "extensions/common/manifest_constants.h"
59 #include "extensions/common/permissions/api_permission.h"
60 #include "extensions/common/permissions/permissions_data.h"
61 #include "extensions/common/verifier_formats.h"
62 #include "mojo/public/cpp/bindings/pending_receiver.h"
63 #include "services/network/public/cpp/shared_url_loader_factory.h"
64 #include "services/network/test/test_network_connection_tracker.h"
65 #include "testing/gtest/include/gtest/gtest.h"
66 
67 #if defined(OS_CHROMEOS)
68 #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
69 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
70 #include "chromeos/dbus/dbus_thread_manager.h"
71 #endif
72 
73 namespace extensions {
74 
75 namespace {
76 
77 const char kTestExtensionName[] = "FooBar";
78 
RequestProxyResolvingSocketFactoryOnUIThread(Profile * profile,base::WeakPtr<gcm::GCMProfileService> service,mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory> receiver)79 void RequestProxyResolvingSocketFactoryOnUIThread(
80     Profile* profile,
81     base::WeakPtr<gcm::GCMProfileService> service,
82     mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>
83         receiver) {
84   if (!service)
85     return;
86   network::mojom::NetworkContext* network_context =
87       content::BrowserContext::GetDefaultStoragePartition(profile)
88           ->GetNetworkContext();
89   network_context->CreateProxyResolvingSocketFactory(std::move(receiver));
90 }
91 
RequestProxyResolvingSocketFactory(Profile * profile,base::WeakPtr<gcm::GCMProfileService> service,mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory> receiver)92 void RequestProxyResolvingSocketFactory(
93     Profile* profile,
94     base::WeakPtr<gcm::GCMProfileService> service,
95     mojo::PendingReceiver<network::mojom::ProxyResolvingSocketFactory>
96         receiver) {
97   content::GetUIThreadTaskRunner({})->PostTask(
98       FROM_HERE, base::BindOnce(&RequestProxyResolvingSocketFactoryOnUIThread,
99                                 profile, service, std::move(receiver)));
100 }
101 
102 }  // namespace
103 
104 // Helper class for asynchronous waiting.
105 class Waiter {
106  public:
Waiter()107   Waiter() {}
~Waiter()108   ~Waiter() {}
109 
110   // Waits until the asynchronous operation finishes.
WaitUntilCompleted()111   void WaitUntilCompleted() {
112     run_loop_.reset(new base::RunLoop);
113     run_loop_->Run();
114   }
115 
116   // Signals that the asynchronous operation finishes.
SignalCompleted()117   void SignalCompleted() {
118     if (run_loop_ && run_loop_->running())
119       run_loop_->Quit();
120   }
121 
122   // Runs until UI loop becomes idle.
PumpUILoop()123   void PumpUILoop() { base::RunLoop().RunUntilIdle(); }
124 
125   // Runs until IO loop becomes idle.
PumpIOLoop()126   void PumpIOLoop() {
127     content::GetIOThreadTaskRunner({})->PostTask(
128         FROM_HERE,
129         base::BindOnce(&Waiter::OnIOLoopPump, base::Unretained(this)));
130 
131     WaitUntilCompleted();
132   }
133 
134  private:
PumpIOLoopCompleted()135   void PumpIOLoopCompleted() {
136     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
137 
138     SignalCompleted();
139   }
140 
OnIOLoopPump()141   void OnIOLoopPump() {
142     DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
143 
144     content::GetIOThreadTaskRunner({})->PostTask(
145         FROM_HERE,
146         base::BindOnce(&Waiter::OnIOLoopPumpCompleted, base::Unretained(this)));
147   }
148 
OnIOLoopPumpCompleted()149   void OnIOLoopPumpCompleted() {
150     DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
151 
152     content::GetUIThreadTaskRunner({})->PostTask(
153         FROM_HERE,
154         base::BindOnce(&Waiter::PumpIOLoopCompleted, base::Unretained(this)));
155   }
156 
157   std::unique_ptr<base::RunLoop> run_loop_;
158 
159   DISALLOW_COPY_AND_ASSIGN(Waiter);
160 };
161 
162 class FakeExtensionGCMAppHandler : public ExtensionGCMAppHandler {
163  public:
FakeExtensionGCMAppHandler(Profile * profile,Waiter * waiter)164   FakeExtensionGCMAppHandler(Profile* profile, Waiter* waiter)
165       : ExtensionGCMAppHandler(profile),
166         waiter_(waiter),
167         unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR),
168         delete_id_result_(instance_id::InstanceID::UNKNOWN_ERROR),
169         app_handler_count_drop_to_zero_(false) {
170   }
171 
~FakeExtensionGCMAppHandler()172   ~FakeExtensionGCMAppHandler() override {}
173 
OnMessage(const std::string & app_id,const gcm::IncomingMessage & message)174   void OnMessage(const std::string& app_id,
175                  const gcm::IncomingMessage& message) override {}
176 
OnMessagesDeleted(const std::string & app_id)177   void OnMessagesDeleted(const std::string& app_id) override {}
178 
OnSendError(const std::string & app_id,const gcm::GCMClient::SendErrorDetails & send_error_details)179   void OnSendError(
180       const std::string& app_id,
181       const gcm::GCMClient::SendErrorDetails& send_error_details) override {}
182 
OnUnregisterCompleted(const std::string & app_id,gcm::GCMClient::Result result)183   void OnUnregisterCompleted(const std::string& app_id,
184                              gcm::GCMClient::Result result) override {
185     ExtensionGCMAppHandler::OnUnregisterCompleted(app_id, result);
186     unregistration_result_ = result;
187     waiter_->SignalCompleted();
188   }
189 
OnDeleteIDCompleted(const std::string & app_id,instance_id::InstanceID::Result result)190   void OnDeleteIDCompleted(const std::string& app_id,
191                            instance_id::InstanceID::Result result) override {
192     delete_id_result_ = result;
193     ExtensionGCMAppHandler::OnDeleteIDCompleted(app_id, result);
194   }
195 
RemoveAppHandler(const std::string & app_id)196   void RemoveAppHandler(const std::string& app_id) override {
197     ExtensionGCMAppHandler::RemoveAppHandler(app_id);
198     if (GetGCMDriver()->app_handlers().empty())
199       app_handler_count_drop_to_zero_ = true;
200   }
201 
unregistration_result() const202   gcm::GCMClient::Result unregistration_result() const {
203     return unregistration_result_;
204   }
delete_id_result() const205   instance_id::InstanceID::Result delete_id_result() const {
206     return delete_id_result_;
207   }
app_handler_count_drop_to_zero() const208   bool app_handler_count_drop_to_zero() const {
209     return app_handler_count_drop_to_zero_;
210   }
211 
212  private:
213   Waiter* waiter_;
214   gcm::GCMClient::Result unregistration_result_;
215   instance_id::InstanceID::Result delete_id_result_;
216   bool app_handler_count_drop_to_zero_;
217 
218   DISALLOW_COPY_AND_ASSIGN(FakeExtensionGCMAppHandler);
219 };
220 
221 class ExtensionGCMAppHandlerTest : public testing::Test {
222  public:
BuildGCMProfileService(content::BrowserContext * context)223   static std::unique_ptr<KeyedService> BuildGCMProfileService(
224       content::BrowserContext* context) {
225     Profile* profile = Profile::FromBrowserContext(context);
226     scoped_refptr<base::SequencedTaskRunner> ui_thread =
227         content::GetUIThreadTaskRunner({});
228     scoped_refptr<base::SequencedTaskRunner> io_thread =
229         content::GetIOThreadTaskRunner({});
230     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner(
231         base::ThreadPool::CreateSequencedTaskRunner(
232             {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
233     return std::make_unique<gcm::GCMProfileService>(
234         profile->GetPrefs(), profile->GetPath(),
235         base::BindRepeating(&RequestProxyResolvingSocketFactory, profile),
236         content::BrowserContext::GetDefaultStoragePartition(profile)
237             ->GetURLLoaderFactoryForBrowserProcess(),
238         network::TestNetworkConnectionTracker::GetInstance(),
239         chrome::GetChannel(),
240         gcm::GetProductCategoryForSubtypes(profile->GetPrefs()),
241         IdentityManagerFactory::GetForProfile(profile),
242         base::WrapUnique(new gcm::FakeGCMClientFactory(ui_thread, io_thread)),
243         ui_thread, io_thread, blocking_task_runner);
244   }
245 
ExtensionGCMAppHandlerTest()246   ExtensionGCMAppHandlerTest()
247       : task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
248         extension_service_(nullptr),
249         registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
250         unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR) {}
251 
~ExtensionGCMAppHandlerTest()252   ~ExtensionGCMAppHandlerTest() override {}
253 
254   // Overridden from test::Test:
SetUp()255   void SetUp() override {
256     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
257 
258     // Allow extension update to unpack crx in process.
259     in_process_utility_thread_helper_.reset(
260         new content::InProcessUtilityThreadHelper);
261 
262     // This is needed to create extension service under CrOS.
263 #if defined(OS_CHROMEOS)
264     test_user_manager_.reset(new chromeos::ScopedTestUserManager());
265     // Creating a DBus thread manager setter has the side effect of
266     // creating a DBusThreadManager, which is needed for testing.
267     // We don't actually need the setter so we ignore the return value.
268     chromeos::DBusThreadManager::GetSetterForTesting();
269 #endif
270 
271     // Create a new profile.
272     TestingProfile::Builder builder;
273     profile_ = builder.Build();
274 
275     // Create extension service in order to uninstall the extension.
276     TestExtensionSystem* extension_system(
277         static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile())));
278     base::FilePath extensions_install_dir =
279         temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Extensions"));
280     extension_system->CreateExtensionService(
281         base::CommandLine::ForCurrentProcess(), extensions_install_dir, false);
282     extension_service_ = extension_system->Get(profile())->extension_service();
283 
284     // Create GCMProfileService that talks with fake GCMClient.
285     gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactoryAndUse(
286         profile(), base::BindRepeating(
287                        &ExtensionGCMAppHandlerTest::BuildGCMProfileService));
288 
289     // Create a fake version of ExtensionGCMAppHandler.
290     gcm_app_handler_.reset(new FakeExtensionGCMAppHandler(profile(), &waiter_));
291   }
292 
TearDown()293   void TearDown() override {
294 #if defined(OS_CHROMEOS)
295     test_user_manager_.reset();
296 #endif
297 
298     waiter_.PumpUILoop();
299     gcm_app_handler_->Shutdown();
300     auto* partition =
301         content::BrowserContext::GetDefaultStoragePartition(profile());
302     if (partition)
303       partition->WaitForDeletionTasksForTesting();
304   }
305 
306   // Returns a barebones test extension.
CreateExtension()307   scoped_refptr<const Extension> CreateExtension() {
308     scoped_refptr<const Extension> extension =
309         ExtensionBuilder(kTestExtensionName)
310             .AddPermission("gcm")
311             .SetPath(temp_dir_.GetPath())
312             .SetID("ldnnhddmnhbkjipkidpdiheffobcpfmf")
313             .Build();
314     EXPECT_TRUE(
315         extension->permissions_data()->HasAPIPermission(APIPermission::kGcm));
316 
317     return extension;
318   }
319 
LoadExtension(const Extension * extension)320   void LoadExtension(const Extension* extension) {
321     extension_service_->AddExtension(extension);
322   }
323 
IsCrxInstallerDone(extensions::CrxInstaller ** installer,const content::NotificationSource & source,const content::NotificationDetails & details)324   static bool IsCrxInstallerDone(extensions::CrxInstaller** installer,
325                                  const content::NotificationSource& source,
326                                  const content::NotificationDetails& details) {
327     return content::Source<extensions::CrxInstaller>(source).ptr() ==
328            *installer;
329   }
330 
UpdateExtension(const Extension * extension,const std::string & update_crx)331   void UpdateExtension(const Extension* extension,
332                        const std::string& update_crx) {
333     base::FilePath data_dir;
334     if (!base::PathService::Get(chrome::DIR_TEST_DATA, &data_dir)) {
335       ADD_FAILURE();
336       return;
337     }
338     data_dir = data_dir.AppendASCII("extensions");
339     data_dir = data_dir.AppendASCII(update_crx);
340 
341     base::FilePath path = temp_dir_.GetPath();
342     path = path.Append(data_dir.BaseName());
343     ASSERT_TRUE(base::CopyFile(data_dir, path));
344 
345     extensions::CrxInstaller* installer = NULL;
346     content::WindowedNotificationObserver observer(
347         extensions::NOTIFICATION_CRX_INSTALLER_DONE,
348         base::Bind(&IsCrxInstallerDone, &installer));
349     extensions::CRXFileInfo crx_info(path, extensions::GetTestVerifierFormat());
350     crx_info.extension_id = extension->id();
351     extension_service_->UpdateExtension(crx_info, true, &installer);
352 
353     if (installer)
354       observer.Wait();
355   }
356 
DisableExtension(const Extension * extension)357   void DisableExtension(const Extension* extension) {
358     extension_service_->DisableExtension(extension->id(),
359                                          disable_reason::DISABLE_USER_ACTION);
360   }
361 
EnableExtension(const Extension * extension)362   void EnableExtension(const Extension* extension) {
363     extension_service_->EnableExtension(extension->id());
364   }
365 
UninstallExtension(const Extension * extension)366   void UninstallExtension(const Extension* extension) {
367     extension_service_->UninstallExtension(
368         extension->id(),
369         extensions::UNINSTALL_REASON_FOR_TESTING,
370         NULL);
371   }
372 
Register(const std::string & app_id,const std::vector<std::string> & sender_ids)373   void Register(const std::string& app_id,
374                 const std::vector<std::string>& sender_ids) {
375     GetGCMDriver()->Register(
376         app_id, sender_ids,
377         base::BindOnce(&ExtensionGCMAppHandlerTest::RegisterCompleted,
378                        base::Unretained(this)));
379   }
380 
RegisterCompleted(const std::string & registration_id,gcm::GCMClient::Result result)381   void RegisterCompleted(const std::string& registration_id,
382                          gcm::GCMClient::Result result) {
383     registration_result_ = result;
384     waiter_.SignalCompleted();
385   }
386 
GetGCMDriver() const387   gcm::GCMDriver* GetGCMDriver() const {
388     return gcm::GCMProfileServiceFactory::GetForProfile(profile())->driver();
389   }
390 
HasAppHandlers(const std::string & app_id) const391   bool HasAppHandlers(const std::string& app_id) const {
392     return GetGCMDriver()->app_handlers().count(app_id);
393   }
394 
profile() const395   Profile* profile() const { return profile_.get(); }
waiter()396   Waiter* waiter() { return &waiter_; }
gcm_app_handler() const397   FakeExtensionGCMAppHandler* gcm_app_handler() const {
398     return gcm_app_handler_.get();
399   }
registration_result() const400   gcm::GCMClient::Result registration_result() const {
401     return registration_result_;
402   }
unregistration_result() const403   gcm::GCMClient::Result unregistration_result() const {
404     return unregistration_result_;
405   }
406 
407  private:
408   content::BrowserTaskEnvironment task_environment_;
409   std::unique_ptr<content::InProcessUtilityThreadHelper>
410       in_process_utility_thread_helper_;
411   std::unique_ptr<TestingProfile> profile_;
412   ExtensionService* extension_service_;  // Not owned.
413   base::ScopedTempDir temp_dir_;
414 
415   // This is needed to create extension service under CrOS.
416 #if defined(OS_CHROMEOS)
417   chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
418   std::unique_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
419 #endif
420 
421   Waiter waiter_;
422   std::unique_ptr<FakeExtensionGCMAppHandler> gcm_app_handler_;
423   gcm::GCMClient::Result registration_result_;
424   gcm::GCMClient::Result unregistration_result_;
425 
426   DISALLOW_COPY_AND_ASSIGN(ExtensionGCMAppHandlerTest);
427 };
428 
TEST_F(ExtensionGCMAppHandlerTest,AddAndRemoveAppHandler)429 TEST_F(ExtensionGCMAppHandlerTest, AddAndRemoveAppHandler) {
430   scoped_refptr<const Extension> extension(CreateExtension());
431 
432   // App handler is added when extension is loaded.
433   LoadExtension(extension.get());
434   waiter()->PumpUILoop();
435   EXPECT_TRUE(HasAppHandlers(extension->id()));
436 
437   // App handler is removed when extension is unloaded.
438   DisableExtension(extension.get());
439   waiter()->PumpUILoop();
440   EXPECT_FALSE(HasAppHandlers(extension->id()));
441 
442   // App handler is added when extension is reloaded.
443   EnableExtension(extension.get());
444   waiter()->PumpUILoop();
445   EXPECT_TRUE(HasAppHandlers(extension->id()));
446 
447   // App handler is removed when extension is uninstalled.
448   UninstallExtension(extension.get());
449   waiter()->WaitUntilCompleted();
450   EXPECT_FALSE(HasAppHandlers(extension->id()));
451 }
452 
TEST_F(ExtensionGCMAppHandlerTest,UnregisterOnExtensionUninstall)453 TEST_F(ExtensionGCMAppHandlerTest, UnregisterOnExtensionUninstall) {
454   scoped_refptr<const Extension> extension(CreateExtension());
455   LoadExtension(extension.get());
456 
457   // Kick off registration.
458   std::vector<std::string> sender_ids;
459   sender_ids.push_back("sender1");
460   Register(extension->id(), sender_ids);
461   waiter()->WaitUntilCompleted();
462   EXPECT_EQ(gcm::GCMClient::SUCCESS, registration_result());
463 
464   // Both token deletion and unregistration should be triggered when the
465   // extension is uninstalled.
466   UninstallExtension(extension.get());
467   waiter()->WaitUntilCompleted();
468   EXPECT_EQ(instance_id::InstanceID::SUCCESS,
469             gcm_app_handler()->delete_id_result());
470   EXPECT_EQ(gcm::GCMClient::SUCCESS,
471             gcm_app_handler()->unregistration_result());
472 }
473 
TEST_F(ExtensionGCMAppHandlerTest,UpdateExtensionWithGcmPermissionKept)474 TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionKept) {
475   scoped_refptr<const Extension> extension(CreateExtension());
476 
477   // App handler is added when the extension is loaded.
478   LoadExtension(extension.get());
479   waiter()->PumpUILoop();
480   EXPECT_TRUE(HasAppHandlers(extension->id()));
481 
482   // App handler count should not drop to zero when the extension is updated.
483   UpdateExtension(extension.get(), "gcm2.crx");
484   waiter()->PumpUILoop();
485   EXPECT_FALSE(gcm_app_handler()->app_handler_count_drop_to_zero());
486   EXPECT_TRUE(HasAppHandlers(extension->id()));
487 }
488 
TEST_F(ExtensionGCMAppHandlerTest,UpdateExtensionWithGcmPermissionRemoved)489 TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionRemoved) {
490   scoped_refptr<const Extension> extension(CreateExtension());
491 
492   // App handler is added when the extension is loaded.
493   LoadExtension(extension.get());
494   waiter()->PumpUILoop();
495   EXPECT_TRUE(HasAppHandlers(extension->id()));
496 
497   // App handler is removed when the extension is updated to the version that
498   // has GCM permission removed.
499   UpdateExtension(extension.get(), "good2.crx");
500   waiter()->PumpUILoop();
501   EXPECT_TRUE(gcm_app_handler()->app_handler_count_drop_to_zero());
502   EXPECT_FALSE(HasAppHandlers(extension->id()));
503 }
504 
505 }  // namespace extensions
506