Create WebPushSender for sending web-push messages

- Created WebPushSender for sending out web-push messages using VAPID
  (https://developers.google.com/web/ilt/pwa/introduction-to-push-notifications#using_vapid)
- Created WebPushMessage struct to encapsulate message_id into messages
  to make callbacks easier
- Refactored across GCMEncryptionProvider / GCMDriver to use
  WebPushMessage and provide callback mechanism

Bug: 966037
Change-Id: I1e8fe6d418e57660ecfe7e8d493b7ae9e71a68f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1649165
Commit-Queue: Alex Chau <[email protected]>
Reviewed-by: Max Moroz <[email protected]>
Reviewed-by: Adam Langley <[email protected]>
Reviewed-by: Scott Violet <[email protected]>
Reviewed-by: Daniel Cheng <[email protected]>
Reviewed-by: Peter Beverloo <[email protected]>
Reviewed-by: Richard Knoll <[email protected]>
Auto-Submit: Alex Chau <[email protected]>
Cr-Commit-Position: refs/heads/master@{#672534}
diff --git a/components/gcm_driver/gcm_driver_unittest.cc b/components/gcm_driver/gcm_driver_unittest.cc
new file mode 100644
index 0000000..71809442
--- /dev/null
+++ b/components/gcm_driver/gcm_driver_unittest.cc
@@ -0,0 +1,285 @@
+// Copyright 2019 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 "components/gcm_driver/gcm_driver_desktop.h"
+
+#include <stdint.h>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/gcm_driver/crypto/gcm_encryption_provider.h"
+#include "components/gcm_driver/fake_gcm_client_factory.h"
+#include "components/gcm_driver/gcm_channel_status_request.h"
+#include "components/gcm_driver/gcm_channel_status_syncer.h"
+#include "components/gcm_driver/gcm_client_factory.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "crypto/ec_private_key.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_network_connection_tracker.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gcm {
+
+namespace {
+
+const char kTestChannelStatusRequestURL[] = "http://channel.status.request.com";
+const char kTestAppID1[] = "TestApp1";
+
+// P-256 point in uncompressed X9.62 format. Used for Diffie-Hellman.
+const char kP256dh[] =
+    "BIYKm1hwEtmAUNfeX3V4cs089gVo0ng+"
+    "UdRgpTFnnv7lojCKRwXgHQZKvjO34fSb7HxQlSRoldcE2iBG2aO468U=";
+// A base64 encoded authentication secret of 128 bits.
+const char kAuthSecret[] = "JX7sXZrnCvwEkepEzP8eAQ==";
+// PKCS #8 encoded P-256 private key.
+const char kPrivateKey[] =
+    "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgS8wRbDOWz0lKExvIVQiRKtPAP8"
+    "dgHUHAw5gyOd5d4jKhRANCAARZb49Va5MD/KcWtc0oiWc2e8njBDtQzj0mzcOl1fDSt16Pvu6p"
+    "fTU3MTWnImDNnkPxtXm58K7Uax8jFxA4TeXJ";
+const char kFCMToken[] = "fcm_token";
+
+void PumpCurrentLoop() {
+  base::RunLoop(base::RunLoop::Type::kNestableTasksAllowed).RunUntilIdle();
+}
+
+}  // namespace
+
+class GCMDriverBaseTest : public testing::Test {
+ public:
+  enum WaitToFinish { DO_NOT_WAIT, WAIT };
+
+  GCMDriverBaseTest();
+  ~GCMDriverBaseTest() override;
+
+  // testing::Test:
+  void SetUp() override;
+  void TearDown() override;
+
+  GCMDriverDesktop* driver() { return driver_.get(); }
+  const std::string& send_web_push_message_id() const {
+    return send_web_push_message_id_;
+  }
+  bool send_web_push_message_result() const {
+    return send_web_push_message_result_;
+  }
+  const std::string& p256dh() const { return p256dh_; }
+  const std::string& auth_secret() const { return auth_secret_; }
+  network::TestURLLoaderFactory& loader() { return test_url_loader_factory_; }
+
+  void PumpIOLoop();
+
+  void CreateDriver();
+  void ShutdownDriver();
+
+  void SendWebPushMessage(const std::string& app_id,
+                          WebPushMessage message,
+                          base::Optional<net::HttpStatusCode> completion_status,
+                          WaitToFinish wait_to_finish);
+  void GetEncryptionInfo(const std::string& app_id,
+                         WaitToFinish wait_to_finish);
+
+  void SendWebPushMessageCompleted(const std::string& message_id, bool result);
+  void GetEncryptionInfoCompleted(const std::string& p256dh,
+                                  const std::string& auth_secret);
+  void UnregisterCompleted(GCMClient::Result result);
+
+ private:
+  base::ScopedTempDir temp_dir_;
+  TestingPrefServiceSimple prefs_;
+  base::test::ScopedTaskEnvironment task_environment_{
+      base::test::ScopedTaskEnvironment::MainThreadType::UI};
+  base::Thread io_thread_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
+
+  std::unique_ptr<GCMDriverDesktop> driver_;
+
+  base::Closure async_operation_completed_callback_;
+
+  std::string send_web_push_message_id_;
+  bool send_web_push_message_result_;
+  std::string p256dh_;
+  std::string auth_secret_;
+
+  DISALLOW_COPY_AND_ASSIGN(GCMDriverBaseTest);
+};
+
+GCMDriverBaseTest::GCMDriverBaseTest() : io_thread_("IOThread") {}
+
+GCMDriverBaseTest::~GCMDriverBaseTest() = default;
+
+void GCMDriverBaseTest::SetUp() {
+  GCMChannelStatusSyncer::RegisterPrefs(prefs_.registry());
+  io_thread_.Start();
+  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+  CreateDriver();
+  PumpIOLoop();
+  PumpCurrentLoop();
+}
+
+void GCMDriverBaseTest::TearDown() {
+  if (!driver_)
+    return;
+
+  ShutdownDriver();
+  driver_.reset();
+  PumpIOLoop();
+
+  io_thread_.Stop();
+}
+
+void GCMDriverBaseTest::PumpIOLoop() {
+  base::RunLoop run_loop;
+  io_thread_.task_runner()->PostTaskAndReply(
+      FROM_HERE, base::BindOnce(&PumpCurrentLoop), run_loop.QuitClosure());
+  run_loop.Run();
+}
+
+void GCMDriverBaseTest::CreateDriver() {
+  scoped_refptr<net::URLRequestContextGetter> request_context =
+      new net::TestURLRequestContextGetter(io_thread_.task_runner());
+  GCMClient::ChromeBuildInfo chrome_build_info;
+  chrome_build_info.product_category_for_subtypes = "com.chrome.macosx";
+  driver_ = std::make_unique<GCMDriverDesktop>(
+      std::make_unique<FakeGCMClientFactory>(
+          base::ThreadTaskRunnerHandle::Get(), io_thread_.task_runner()),
+      chrome_build_info, kTestChannelStatusRequestURL, "user-agent-string",
+      &prefs_, temp_dir_.GetPath(), base::DoNothing(),
+      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+          &test_url_loader_factory_),
+      network::TestNetworkConnectionTracker::GetInstance(),
+      base::ThreadTaskRunnerHandle::Get(), io_thread_.task_runner(),
+      task_environment_.GetMainThreadTaskRunner());
+}
+
+void GCMDriverBaseTest::ShutdownDriver() {
+  driver()->Shutdown();
+}
+
+void GCMDriverBaseTest::SendWebPushMessage(
+    const std::string& app_id,
+    WebPushMessage message,
+    base::Optional<net::HttpStatusCode> completion_status,
+    WaitToFinish wait_to_finish) {
+  std::string p256dh, auth_secret;
+  ASSERT_TRUE(base::Base64Decode(kP256dh, &p256dh));
+  ASSERT_TRUE(base::Base64Decode(kAuthSecret, &auth_secret));
+
+  std::string private_key_info;
+  ASSERT_TRUE(base::Base64Decode(kPrivateKey, &private_key_info));
+  std::unique_ptr<crypto::ECPrivateKey> private_key =
+      crypto::ECPrivateKey::CreateFromPrivateKeyInfo(std::vector<uint8_t>(
+          private_key_info.begin(), private_key_info.end()));
+  ASSERT_TRUE(private_key);
+
+  base::RunLoop run_loop;
+  async_operation_completed_callback_ = run_loop.QuitClosure();
+  driver_->SendWebPushMessage(
+      app_id, /* authorized_entity= */ "", p256dh, auth_secret, kFCMToken,
+      private_key.get(), std::move(message),
+      base::BindOnce(&GCMDriverBaseTest::SendWebPushMessageCompleted,
+                     base::Unretained(this)));
+
+  if (completion_status) {
+    ASSERT_EQ(test_url_loader_factory_.NumPending(), 1);
+    test_url_loader_factory_.SimulateResponseForPendingRequest(
+        test_url_loader_factory_.GetPendingRequest(0)->request.url,
+        network::URLLoaderCompletionStatus(net::OK),
+        network::CreateResourceResponseHead(*completion_status), "");
+  }
+
+  if (wait_to_finish == WAIT)
+    run_loop.Run();
+}
+
+void GCMDriverBaseTest::GetEncryptionInfo(const std::string& app_id,
+                                          WaitToFinish wait_to_finish) {
+  base::RunLoop run_loop;
+  async_operation_completed_callback_ = run_loop.QuitClosure();
+  driver_->GetEncryptionInfo(
+      app_id, base::Bind(&GCMDriverBaseTest::GetEncryptionInfoCompleted,
+                         base::Unretained(this)));
+  if (wait_to_finish == WAIT)
+    run_loop.Run();
+}
+
+void GCMDriverBaseTest::SendWebPushMessageCompleted(
+    const std::string& message_id,
+    bool result) {
+  send_web_push_message_id_ = message_id;
+  send_web_push_message_result_ = result;
+  if (!async_operation_completed_callback_.is_null())
+    async_operation_completed_callback_.Run();
+}
+
+void GCMDriverBaseTest::GetEncryptionInfoCompleted(
+    const std::string& p256dh,
+    const std::string& auth_secret) {
+  p256dh_ = p256dh;
+  auth_secret_ = auth_secret;
+  if (!async_operation_completed_callback_.is_null())
+    async_operation_completed_callback_.Run();
+}
+
+TEST_F(GCMDriverBaseTest, SendWebPushMessage) {
+  GetEncryptionInfo(kTestAppID1, GCMDriverBaseTest::WAIT);
+
+  WebPushMessage message;
+  message.id = "message_id";
+  message.time_to_live = 3600;
+  message.payload = "payload";
+  ASSERT_NO_FATAL_FAILURE(SendWebPushMessage(kTestAppID1, std::move(message),
+                                             base::make_optional(net::HTTP_OK),
+                                             GCMDriverBaseTest::WAIT));
+
+  EXPECT_EQ("message_id", send_web_push_message_id());
+  EXPECT_TRUE(send_web_push_message_result());
+}
+
+TEST_F(GCMDriverBaseTest, SendWebPushMessageEncryptionError) {
+  // Intentionally not creating encryption info.
+
+  WebPushMessage message;
+  message.id = "message_id";
+  message.time_to_live = 3600;
+  message.payload = "payload";
+  ASSERT_NO_FATAL_FAILURE(SendWebPushMessage(
+      kTestAppID1, std::move(message), base::nullopt, GCMDriverBaseTest::WAIT));
+
+  EXPECT_EQ("message_id", send_web_push_message_id());
+  EXPECT_FALSE(send_web_push_message_result());
+}
+
+TEST_F(GCMDriverBaseTest, SendWebPushMessageServerError) {
+  GetEncryptionInfo(kTestAppID1, GCMDriverBaseTest::WAIT);
+
+  WebPushMessage message;
+  message.id = "message_id";
+  message.time_to_live = 3600;
+  message.payload = "payload";
+  ASSERT_NO_FATAL_FAILURE(
+      SendWebPushMessage(kTestAppID1, std::move(message),
+                         base::make_optional(net::HTTP_INTERNAL_SERVER_ERROR),
+                         GCMDriverBaseTest::WAIT));
+
+  EXPECT_EQ("message_id", send_web_push_message_id());
+  EXPECT_FALSE(send_web_push_message_result());
+}
+
+}  // namespace gcm