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