// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/public/browser/push_messaging_service.h" #include #include #include #include "base/command_line.h" #include "base/macros.h" #include "base/run_loop.h" #include "base/thread_task_runner_handle.h" #include "chrome/browser/permissions/permission_manager.h" #include "chrome/browser/permissions/permission_manager_factory.h" #include "chrome/browser/push_messaging/push_messaging_app_identifier.h" #include "chrome/browser/push_messaging/push_messaging_permission_context.h" #include "chrome/browser/push_messaging/push_messaging_permission_context_factory.h" #include "chrome/browser/push_messaging/push_messaging_service_factory.h" #include "chrome/browser/push_messaging/push_messaging_service_impl.h" #include "chrome/browser/services/gcm/fake_gcm_profile_service.h" #include "chrome/browser/services/gcm/gcm_profile_service_factory.h" #include "chrome/test/base/testing_profile.h" #include "components/gcm_driver/crypto/gcm_crypto_test_helpers.h" #include "components/gcm_driver/fake_gcm_client_factory.h" #include "components/gcm_driver/gcm_profile_service.h" #include "content/public/common/content_switches.h" #include "content/public/common/push_event_payload.h" #include "content/public/test/test_browser_thread_bundle.h" #include "testing/gtest/include/gtest/gtest.h" namespace { const char kTestOrigin[] = "https://example.com"; const char kTestSenderId[] = "1234567890"; const int64_t kTestServiceWorkerId = 42; const char kTestPayload[] = "Hello, world!"; // Implementation of the TestingProfile that provides the Push Messaging Service // and the Permission Manager, both of which are required for the tests. class PushMessagingTestingProfile : public TestingProfile { public: PushMessagingTestingProfile() {} ~PushMessagingTestingProfile() override {} PushMessagingServiceImpl* GetPushMessagingService() override { return PushMessagingServiceFactory::GetForProfile(this); } PermissionManager* GetPermissionManager() override { return PermissionManagerFactory::GetForProfile(this); } private: DISALLOW_COPY_AND_ASSIGN(PushMessagingTestingProfile); }; // Implementation of the PushMessagingPermissionContext that always allows usage // of Push Messaging, regardless of the actual implementation. class FakePushMessagingPermissionContext : public PushMessagingPermissionContext { public: explicit FakePushMessagingPermissionContext(Profile* profile) : PushMessagingPermissionContext(profile) {} ~FakePushMessagingPermissionContext() override {} ContentSetting GetPermissionStatus( const GURL& requesting_origin, const GURL& embedding_origin) const override { return CONTENT_SETTING_ALLOW; } }; scoped_ptr BuildFakePushMessagingPermissionContext( content::BrowserContext* context) { return scoped_ptr( new FakePushMessagingPermissionContext(static_cast(context))); } scoped_ptr BuildFakeGCMProfileService( content::BrowserContext* context) { return gcm::FakeGCMProfileService::Build(static_cast(context)); } } // namespace class PushMessagingServiceTest : public ::testing::Test { public: PushMessagingServiceTest() { // Override the permission context factory to always allow Push Messaging. PushMessagingPermissionContextFactory::GetInstance()->SetTestingFactory( &profile_, &BuildFakePushMessagingPermissionContext); // Override the GCM Profile service so that we can send fake messages. gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactory( &profile_, &BuildFakeGCMProfileService); // Force-enable encrypted payloads for incoming push messages. base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableExperimentalWebPlatformFeatures); } ~PushMessagingServiceTest() override {} // Callback to use when the subscription may have been subscribed. void DidRegister(std::string* subscription_id_out, std::vector* p256dh_out, std::vector* auth_out, const std::string& registration_id, const std::vector& p256dh, const std::vector& auth, content::PushRegistrationStatus status) { ASSERT_EQ(content::PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE, status); *subscription_id_out = registration_id; *p256dh_out = p256dh; *auth_out = auth; } // Callback to use when observing messages dispatched by the push service. void DidDispatchMessage(std::string* app_id_out, GURL* origin_out, int64_t* service_worker_registration_id_out, content::PushEventPayload* payload_out, const std::string& app_id, const GURL& origin, int64_t service_worker_registration_id, const content::PushEventPayload& payload) { *app_id_out = app_id; *origin_out = origin; *service_worker_registration_id_out = service_worker_registration_id; *payload_out = payload; } protected: PushMessagingTestingProfile* profile() { return &profile_; } private: content::TestBrowserThreadBundle thread_bundle_; PushMessagingTestingProfile profile_; }; TEST_F(PushMessagingServiceTest, PayloadEncryptionTest) { PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService(); ASSERT_TRUE(push_service); const GURL origin(kTestOrigin); // (1) Make sure that |kExampleOrigin| has access to use Push Messaging. ASSERT_EQ(blink::WebPushPermissionStatusGranted, push_service->GetPermissionStatus(origin, true)); std::string subscription_id; std::vector p256dh, auth; // (2) Subscribe for Push Messaging, and verify that we've got the required // information in order to be able to create encrypted messages. push_service->SubscribeFromWorker( origin, kTestServiceWorkerId, kTestSenderId, true /* user_visible */, base::Bind(&PushMessagingServiceTest::DidRegister, base::Unretained(this), &subscription_id, &p256dh, &auth)); EXPECT_EQ(0u, subscription_id.size()); // this must be asynchronous base::RunLoop().RunUntilIdle(); ASSERT_GT(subscription_id.size(), 0u); ASSERT_GT(p256dh.size(), 0u); ASSERT_GT(auth.size(), 0u); // (3) Encrypt a message using the public key and authentication secret that // are associated with the subscription. gcm::IncomingMessage message; message.sender_id = kTestSenderId; ASSERT_TRUE(gcm::CreateEncryptedPayloadForTesting( kTestPayload, base::StringPiece(reinterpret_cast(p256dh.data()), p256dh.size()), base::StringPiece(reinterpret_cast(auth.data()), auth.size()), &message)); ASSERT_GT(message.raw_data.size(), 0u); ASSERT_NE(kTestPayload, message.raw_data); ASSERT_FALSE(message.decrypted); // (4) Find the app_id that has been associated with the subscription. PushMessagingAppIdentifier app_identifier = PushMessagingAppIdentifier::FindByServiceWorker(profile(), origin, kTestServiceWorkerId); ASSERT_FALSE(app_identifier.is_null()); std::string app_id; GURL dispatched_origin; int64_t service_worker_registration_id; content::PushEventPayload payload; // (5) Observe message dispatchings from the Push Messaging service, and then // dispatch the |message| on the GCM driver as if it had actually been // received by Google Cloud Messaging. push_service->SetMessageDispatchedCallbackForTesting(base::Bind( &PushMessagingServiceTest::DidDispatchMessage, base::Unretained(this), &app_id, &dispatched_origin, &service_worker_registration_id, &payload)); gcm::FakeGCMProfileService* fake_profile_service = static_cast( gcm::GCMProfileServiceFactory::GetForProfile(profile())); fake_profile_service->DispatchMessage(app_identifier.app_id(), message); base::RunLoop().RunUntilIdle(); // (6) Verify that the message, as received by the Push Messaging Service, has // indeed been decrypted by the GCM Driver, and has been forwarded to the // Service Worker that has been associated with the subscription. EXPECT_EQ(app_identifier.app_id(), app_id); EXPECT_EQ(origin, dispatched_origin); EXPECT_EQ(service_worker_registration_id, kTestServiceWorkerId); EXPECT_FALSE(payload.is_null); EXPECT_EQ(kTestPayload, payload.data); }