// Copyright 2015 The Chromium Authors
// 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 <stdint.h>

#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/gcm/gcm_profile_service_factory.h"
#include "chrome/browser/global_features.h"
#include "chrome/browser/history/history_service_factory.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_service_factory.h"
#include "chrome/browser/push_messaging/push_messaging_service_impl.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/gcm_driver/crypto/gcm_crypto_test_helpers.h"
#include "components/gcm_driver/fake_gcm_client_factory.h"
#include "components/gcm_driver/fake_gcm_profile_service.h"
#include "components/gcm_driver/gcm_profile_service.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/permissions/permission_manager.h"
#include "components/permissions/permission_uma_util.h"
#include "components/push_messaging/push_messaging_features.h"
#include "components/push_messaging/push_messaging_utils.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "content/public/browser/permission_result.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service_test_with_install.h"
#include "content/public/browser/permission_result.h"
#include "content/public/test/mock_permission_controller.h"
#include "extensions/common/extension.h"
#include "extensions/test/test_extension_dir.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)

#if BUILDFLAG(IS_ANDROID)
#include "components/gcm_driver/instance_id/instance_id_android.h"
#include "components/gcm_driver/instance_id/scoped_use_fake_instance_id_android.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "url/gurl.h"
#include "url/origin.h"
#endif  // BUILDFLAG(IS_ANDROID)

namespace {

constexpr std::string_view kTestOrigin = "https://example.com";
constexpr std::string_view kTestSenderId = "1234567890";
constexpr int64_t kTestServiceWorkerId = 42;
constexpr std::string_view kTestPayload = "Hello, world!";

// NIST P-256 public key in uncompressed format per SEC1 2.3.3.
constexpr uint8_t kTestP256Key[] = {
    0x04, 0x55, 0x52, 0x6A, 0xA5, 0x6E, 0x8E, 0xAA, 0x47, 0x97, 0x36,
    0x10, 0xC1, 0x66, 0x3C, 0x1E, 0x65, 0xBF, 0xA1, 0x7B, 0xEE, 0x48,
    0xC9, 0xC6, 0xBB, 0xBF, 0x02, 0x18, 0x53, 0x72, 0x1D, 0x0C, 0x7B,
    0xA9, 0xE3, 0x11, 0xB7, 0x03, 0x52, 0x21, 0xD3, 0x71, 0x90, 0x13,
    0xA8, 0xC1, 0xCF, 0xED, 0x20, 0xF7, 0x1F, 0xD1, 0x7F, 0xF2, 0x76,
    0xB6, 0x01, 0x20, 0xD8, 0x35, 0xA5, 0xD9, 0x3C, 0x43, 0xFD};

static_assert(sizeof(kTestP256Key) == 65,
              "The fake public key must be a valid P-256 uncompressed point.");

// URL-safe base64 encoded version of the |kTestP256Key|.
constexpr char kTestEncodedP256Key[] =
    "BFVSaqVujqpHlzYQwWY8HmW_oXvuSMnGu78CGFNyHQx7qeMRtwNSIdNxkBOowc_tIPcf0X_ydr"
    "YBINg1pdk8Q_0";

// 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() = default;

  PushMessagingTestingProfile(const PushMessagingTestingProfile&) = delete;
  PushMessagingTestingProfile& operator=(const PushMessagingTestingProfile&) =
      delete;

  ~PushMessagingTestingProfile() override = default;

  PushMessagingServiceImpl* GetPushMessagingService() override {
    return PushMessagingServiceFactory::GetForProfile(this);
  }

  permissions::PermissionManager* GetPermissionControllerDelegate() override {
    return PermissionManagerFactory::GetForProfile(this);
  }
};

std::unique_ptr<KeyedService> BuildFakeGCMProfileService(
    content::BrowserContext* context) {
  return gcm::FakeGCMProfileService::Build(static_cast<Profile*>(context));
}

std::unique_ptr<KeyedService> BuildTestHistoryService(
    content::BrowserContext* context) {
  auto service = std::make_unique<history::HistoryService>();
  service->Init(history::TestHistoryDatabaseParamsForPath(context->GetPath()));
  return service;
}

}  // namespace

class PushMessagingServiceTest : public ::testing::Test {
 public:
  PushMessagingServiceTest() = default;
  ~PushMessagingServiceTest() override = default;

  void SetUp() override {
    TestingBrowserProcess::GetGlobal()->SetUpGlobalFeaturesForTesting(
        /*profile_manager=*/false);
    profile_ = std::make_unique<PushMessagingTestingProfile>();

    // Override the GCM Profile service so that we can send fake messages.
    gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactory(
        profile_.get(), base::BindRepeating(&BuildFakeGCMProfileService));

    HistoryServiceFactory::GetInstance()->SetTestingFactory(
        profile_.get(), base::BindRepeating(&BuildTestHistoryService));
  }

  void TearDown() override {
    profile_.reset();
    TestingBrowserProcess::GetGlobal()->TearDownGlobalFeaturesForTesting();
  }

  void SetPermission(const GURL& origin, ContentSetting value) {
    HostContentSettingsMap* host_content_settings_map =
        HostContentSettingsMapFactory::GetForProfile(profile_.get());
    host_content_settings_map->SetContentSettingDefaultScope(
        origin, origin, ContentSettingsType::NOTIFICATIONS, value);
  }

  // Callback to use when the subscription may have been subscribed.
  void DidRegister(std::string* subscription_id_out,
                   GURL* endpoint_out,
                   std::optional<base::Time>* expiration_time_out,
                   std::vector<uint8_t>* p256dh_out,
                   std::vector<uint8_t>* auth_out,
                   base::OnceClosure done_callback,
                   const std::string& registration_id,
                   const GURL& endpoint,
                   const std::optional<base::Time>& expiration_time,
                   const std::vector<uint8_t>& p256dh,
                   const std::vector<uint8_t>& auth,
                   blink::mojom::PushRegistrationStatus status) {
    EXPECT_EQ(blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE,
              status);

    *subscription_id_out = registration_id;
    *expiration_time_out = expiration_time;
    *endpoint_out = endpoint;
    *p256dh_out = p256dh;
    *auth_out = auth;

    std::move(done_callback).Run();
  }

  // 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,
      std::optional<std::string>* payload_out,
      const std::string& app_id,
      const GURL& origin,
      int64_t service_worker_registration_id,
      std::optional<std::string> payload,
      PushMessagingServiceImpl::PushEventCallback callback) {
    *app_id_out = app_id;
    *origin_out = origin;
    *service_worker_registration_id_out = service_worker_registration_id;
    *payload_out = std::move(payload);
  }

  class TestPushSubscription {
   public:
    std::string subscription_id_;
    GURL endpoint_;
    std::optional<base::Time> expiration_time_;
    std::vector<uint8_t> p256dh_;
    std::vector<uint8_t> auth_;
    TestPushSubscription(const std::string& subscription_id,
                         const GURL& endpoint,
                         const std::optional<base::Time>& expiration_time,
                         const std::vector<uint8_t>& p256dh,
                         const std::vector<uint8_t>& auth)
        : subscription_id_(subscription_id),
          endpoint_(endpoint),
          expiration_time_(expiration_time),
          p256dh_(p256dh),
          auth_(auth) {}
    TestPushSubscription() = default;
  };

  void Subscribe(PushMessagingServiceImpl* push_service,
                 const GURL& origin,
                 TestPushSubscription* subscription = nullptr) {
    std::string subscription_id;
    GURL endpoint;
    std::optional<base::Time> expiration_time;
    std::vector<uint8_t> p256dh, auth;

    base::RunLoop run_loop;

    auto options = blink::mojom::PushSubscriptionOptions::New();
    options->user_visible_only = true;
    options->application_server_key = std::vector<uint8_t>(
        std::begin(kTestSenderId), std::end(kTestSenderId));

    push_service->SubscribeFromWorker(
        origin, kTestServiceWorkerId, /*render_process_id=*/-1,
        std::move(options),
        base::BindOnce(&PushMessagingServiceTest::DidRegister,
                       base::Unretained(this), &subscription_id, &endpoint,
                       &expiration_time, &p256dh, &auth,
                       run_loop.QuitClosure()));

    EXPECT_EQ(0u, subscription_id.size());  // this must be asynchronous

    run_loop.Run();

    ASSERT_GT(subscription_id.size(), 0u);
    ASSERT_TRUE(endpoint.is_valid());
    ASSERT_GT(endpoint.spec().size(), 0u);
    ASSERT_GT(p256dh.size(), 0u);
    ASSERT_GT(auth.size(), 0u);

    if (subscription) {
      subscription->subscription_id_ = subscription_id;
      subscription->endpoint_ = endpoint;
      subscription->p256dh_ = p256dh;
      subscription->auth_ = auth;
    }
  }

 protected:
  PushMessagingTestingProfile* profile() { return profile_.get(); }

  content::BrowserTaskEnvironment& task_environment() {
    return task_environment_;
  }

 private:
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  std::unique_ptr<PushMessagingTestingProfile> profile_;

#if BUILDFLAG(IS_ANDROID)
  instance_id::InstanceIDAndroid::ScopedBlockOnAsyncTasksForTesting
      block_async_;
#endif  // BUILDFLAG(IS_ANDROID)
};

TEST_F(PushMessagingServiceTest, RecordsRevocationAndSourceUiNoReporterTest) {
  base::HistogramTester histograms;

  PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService();
  ASSERT_TRUE(push_service);

  const GURL origin(kTestOrigin);
  SetPermission(origin, CONTENT_SETTING_ALLOW);

  ASSERT_EQ(blink::mojom::PermissionStatus::GRANTED,
            push_service->GetPermissionStatus(origin, true /* user_visible */));

  Subscribe(push_service, origin);

  SetPermission(origin, CONTENT_SETTING_DEFAULT);

  histograms.ExpectUniqueSample(
      "Permissions.Action.Notifications",
      static_cast<int>(permissions::PermissionAction::REVOKED), 1);
  histograms.ExpectUniqueSample(
      "Permissions.Revocation.Notifications.SourceUI",
      static_cast<int>(permissions::PermissionSourceUI::UNIDENTIFIED), 1);
}

TEST_F(PushMessagingServiceTest, RecordsRevocationAndSourceUiWithReporterTest) {
  base::HistogramTester histograms;

  PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService();
  ASSERT_TRUE(push_service);

  const GURL origin(kTestOrigin);
  SetPermission(origin, CONTENT_SETTING_ALLOW);

  ASSERT_EQ(blink::mojom::PermissionStatus::GRANTED,
            push_service->GetPermissionStatus(origin, true /* user_visible */));

  Subscribe(push_service, origin);

  const auto source_ui = permissions::PermissionSourceUI::SITE_SETTINGS;
  {
    permissions::PermissionUmaUtil::ScopedRevocationReporter
        scoped_revocation_reporter(profile(), origin, origin,
                                   ContentSettingsType::NOTIFICATIONS,
                                   source_ui);
    SetPermission(origin, CONTENT_SETTING_DEFAULT);
  }

  histograms.ExpectUniqueSample(
      "Permissions.Action.Notifications",
      static_cast<int>(permissions::PermissionAction::REVOKED), 1);
  histograms.ExpectUniqueSample("Permissions.Revocation.Notifications.SourceUI",
                                static_cast<int>(source_ui), 1);
}

// Fails too often on Linux TSAN builder: http://crbug.com/40767573.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_PayloadEncryptionTest DISABLED_PayloadEncryptionTest
#else
#define MAYBE_PayloadEncryptionTest PayloadEncryptionTest
#endif
TEST_F(PushMessagingServiceTest, MAYBE_PayloadEncryptionTest) {
  PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService();
  ASSERT_TRUE(push_service);

  const GURL origin(kTestOrigin);
  SetPermission(origin, CONTENT_SETTING_ALLOW);

  // (1) Make sure that |kExampleOrigin| has access to use Push Messaging.
  ASSERT_EQ(blink::mojom::PermissionStatus::GRANTED,
            push_service->GetPermissionStatus(origin, true /* user_visible */));

  // (2) Subscribe for Push Messaging, and verify that we've got the required
  // information in order to be able to create encrypted messages.
  TestPushSubscription subscription;
  Subscribe(push_service, origin, &subscription);

  // (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,
      std::string_view(reinterpret_cast<char*>(subscription.p256dh_.data()),
                       subscription.p256dh_.size()),
      std::string_view(reinterpret_cast<char*>(subscription.auth_.data()),
                       subscription.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.
  push_messaging::AppIdentifier 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;
  std::optional<std::string> 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::BindRepeating(
      &PushMessagingServiceTest::DidDispatchMessage, base::Unretained(this),
      &app_id, &dispatched_origin, &service_worker_registration_id, &payload));

  gcm::FakeGCMProfileService* fake_profile_service =
      static_cast<gcm::FakeGCMProfileService*>(
          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_TRUE(payload);
  EXPECT_EQ(kTestPayload, *payload);
}

TEST_F(PushMessagingServiceTest, ProfileDestructionTest) {
  PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService();
  ASSERT_TRUE(push_service);

  const GURL origin(kTestOrigin);
  SetPermission(origin, CONTENT_SETTING_ALLOW);

  // (1) Make sure that |kExampleOrigin| has access to use Push Messaging.
  ASSERT_EQ(blink::mojom::PermissionStatus::GRANTED,
            push_service->GetPermissionStatus(origin, true /* user_visible */));

  // (2) Subscribe for Push Messaging, and verify that we've got the required
  // information in order to be able to create encrypted messages.
  TestPushSubscription subscription;
  Subscribe(push_service, origin, &subscription);

  // (3) Create a message.
  gcm::IncomingMessage message;
  message.sender_id = kTestSenderId;

  // (4) Find the app_id that has been associated with the subscription.
  push_messaging::AppIdentifier app_identifier =
      PushMessagingAppIdentifier::FindByServiceWorker(profile(), origin,
                                                      kTestServiceWorkerId);

  ASSERT_FALSE(app_identifier.is_null());

  // (5) Observe message dispatchings from the Push Messaging service, and
  // fail the test if any message is dispatched.
  bool did_dispatch = false;
  push_service->SetMessageDispatchedCallbackForTesting(
      base::BindLambdaForTesting(
          [&did_dispatch](
              const std::string& app_id, const GURL& origin,
              int64_t service_worker_registration_id,
              std::optional<std::string> payload,
              PushMessagingServiceImpl::PushEventCallback callback) {
            did_dispatch = true;
          }));

  // Create an active ProfileManager, and do NOT make it the owner of profile().
  // This causes ScopedProfileKeepAlive::TryAcquire() to fail.
  auto testing_profile_manager = std::make_unique<TestingProfileManager>(
      TestingBrowserProcess::GetGlobal());
  ASSERT_TRUE(testing_profile_manager->SetUp());

  gcm::FakeGCMProfileService* fake_profile_service =
      static_cast<gcm::FakeGCMProfileService*>(
          gcm::GCMProfileServiceFactory::GetForProfile(profile()));

  fake_profile_service->DispatchMessage(app_identifier.app_id(), message);

  base::RunLoop().RunUntilIdle();

#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
  EXPECT_FALSE(did_dispatch);
#else
  // Keepalives do nothing on ChromeOS and Android, so the message will always
  // be dispatched.
  EXPECT_TRUE(did_dispatch);
#endif  // BUILDFLAG(ENABLE_BACKGROUND_MODE)
}

TEST_F(PushMessagingServiceTest, NormalizeSenderInfo) {
  PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService();
  ASSERT_TRUE(push_service);

  std::string p256dh(std::begin(kTestP256Key), std::end(kTestP256Key));
  ASSERT_EQ(65u, p256dh.size());

  // NIST P-256 public keys in uncompressed format will be encoded using the
  // URL-safe base64 encoding by the normalization function.
  EXPECT_EQ(kTestEncodedP256Key, push_messaging::NormalizeSenderInfo(p256dh));

  // Any other value, binary or not, will be passed through as-is.
  EXPECT_EQ("1234567890", push_messaging::NormalizeSenderInfo("1234567890"));
  EXPECT_EQ("foo@bar.com", push_messaging::NormalizeSenderInfo("foo@bar.com"));

  p256dh[0] = 0x05;  // invalidate |p256dh| as a public key.

  EXPECT_EQ(p256dh, push_messaging::NormalizeSenderInfo(p256dh));
}

// Fails too often on Linux TSAN builder: http://crbug.com/40767573.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_RemoveExpiredSubscriptions DISABLED_RemoveExpiredSubscriptions
#else
#define MAYBE_RemoveExpiredSubscriptions RemoveExpiredSubscriptions
#endif
TEST_F(PushMessagingServiceTest, MAYBE_RemoveExpiredSubscriptions) {
  // (1) Enable push subscriptions with expiration time and
  // `pushsubscriptionchange` events
  base::test::ScopedFeatureList scoped_feature_list_;
  scoped_feature_list_.InitWithFeatures(
      /* enabled features */
      {features::kPushSubscriptionWithExpirationTime,
       features::kPushSubscriptionChangeEventOnInvalidation},
      /* disabled features */
      {});

  const GURL origin(kTestOrigin);
  SetPermission(origin, CONTENT_SETTING_ALLOW);

  // (2) Set up push service and test origin
  PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService();
  ASSERT_TRUE(push_service);

  // (3) Subscribe origin to push service and find corresponding
  // |app_identifier|
  Subscribe(push_service, origin);
  push_messaging::AppIdentifier app_identifier =
      PushMessagingAppIdentifier::FindByServiceWorker(profile(), origin,
                                                      kTestServiceWorkerId);
  ASSERT_FALSE(app_identifier.is_null());

  // (4) Manually set the time as expired, save the time in preferences
  app_identifier.set_expiration_time(base::Time::UnixEpoch());
  PushMessagingAppIdentifier::PersistToPrefs(app_identifier, profile());
  ASSERT_EQ(1u, PushMessagingAppIdentifier::GetCount(profile()));

  // (3) Remove all expired subscriptions
  base::RunLoop run_loop;
  push_service->SetRemoveExpiredSubscriptionsCallbackForTesting(
      run_loop.QuitClosure());
  push_service->RemoveExpiredSubscriptions();
  run_loop.Run();

  // (5) We expect the subscription to be deleted
  ASSERT_EQ(0u, PushMessagingAppIdentifier::GetCount(profile()));
  push_messaging::AppIdentifier deleted_identifier =
      PushMessagingAppIdentifier::FindByAppId(profile(),
                                              app_identifier.app_id());
  EXPECT_TRUE(deleted_identifier.is_null());
}

// Tests that extensions are permitted to pass userVisibleOnly true or false
// when subscribing to push messages.
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
namespace extensions {

using ContextType = extensions::browser_test_util::ContextType;

class ExtensionsPushMessagingServiceTest
    : public ExtensionServiceTestWithInstall,
      public testing::WithParamInterface<ContextType> {
 public:
  ExtensionsPushMessagingServiceTest() = default;

  ExtensionsPushMessagingServiceTest(
      const ExtensionsPushMessagingServiceTest&) = delete;
  ExtensionsPushMessagingServiceTest& operator=(
      const ExtensionsPushMessagingServiceTest&) = delete;

  void SetUp() override {
    TestingBrowserProcess::GetGlobal()->SetUpGlobalFeaturesForTesting(
        /*profile_manager=*/false);
    ExtensionServiceTestWithInstall::SetUp();
    InitializeExtensionService(ExtensionServiceInitParams());

    // Override the GCM Profile service so that we can use a fake GCM driver to
    // simulate GCM responses in a controlled unit test environment for push
    // testing.
    gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactory(
        profile(), base::BindRepeating(&BuildFakeGCMProfileService));
  }

  void TearDown() override {
    ExtensionServiceTestWithInstall::TearDown();
    TestingBrowserProcess::GetGlobal()->TearDownGlobalFeaturesForTesting();
  }

 private:
  // Required to prevent DCHECK failure in VariationsIdsProvider::GetInstance()
  // when loading the service worker script, as unit tests do not automatically
  // initialize the global variations instance.
  variations::test::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
      variations::VariationsIdsProvider::Mode::kUseSignedInState};
};

// Tests that extension origins with various background contexts have permission
// to the Push API. It tests that they can request userVisible:true when
// subscribing to push notifications. Only worker based extensions are allowed
// to set userVisible:false though.
TEST_P(ExtensionsPushMessagingServiceTest, PushMessagingAPIPermission) {
  static constexpr char kManifestPersistentBackgroundScript[] =
      R"({"scripts": ["background.js"], "persistent": true})";
  static constexpr char kManifestEventPageBackgroundScript[] =
      R"({"persistent": false,
          "scripts": ["background.js"]
         }
      )";
  static constexpr char kManifestServiceWorkerBackgroundScript[] =
      R"({"service_worker": "background.js"})";

  // Load an extension of ContextType.
  TestExtensionDir test_dir;
  constexpr char kManifest[] =
      R"({
         "name": "Test Extension",
         "manifest_version": %s,
         "version": "0.1",
         "background": %s,
         "permissions": ["notifications"]
       })";
  ContextType extension_context_type = GetParam();
  bool worker_extension = extension_context_type == ContextType::kServiceWorker;
  const char* background_script;
  if (worker_extension) {
    background_script = kManifestServiceWorkerBackgroundScript;
  } else if (extension_context_type == ContextType::kEventPage) {
    background_script = kManifestEventPageBackgroundScript;
  } else {
    background_script = kManifestPersistentBackgroundScript;
  }
  const char* manifest_version = worker_extension ? "3" : "2";
  std::string manifest =
      base::StringPrintf(kManifest, manifest_version, background_script);
  test_dir.WriteManifest(manifest);
  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "");
  ChromeTestExtensionLoader loader(profile());
  scoped_refptr<const Extension> extension =
      loader.LoadExtension(test_dir.UnpackedPath());
  ASSERT_TRUE(extension);

  // Assume (with mocking) that notifications permission is granted for
  // extensions per the manifest (for workers and non-workers).
  auto mock_permission_controller =
      std::make_unique<content::MockPermissionController>();
  auto permission_status_mock_return = content::PermissionStatus::GRANTED;
  EXPECT_CALL(*mock_permission_controller,
              GetPermissionStatusForWorker(testing::_, testing::_, testing::_))
      .WillRepeatedly(testing::Return(permission_status_mock_return));
  EXPECT_CALL(*mock_permission_controller,
              GetPermissionStatusForWorker(testing::_, testing::_, testing::_))
      .WillRepeatedly(testing::Return(permission_status_mock_return));
  auto permission_result_mock_return =
      content::PermissionResult(content::PermissionStatus::GRANTED,
                                content::PermissionStatusSource::UNSPECIFIED);
  EXPECT_CALL(*mock_permission_controller,
              GetPermissionResultForOriginWithoutContext(testing::_, testing::_,
                                                         testing::_))
      .WillRepeatedly(testing::Return(permission_result_mock_return));
  EXPECT_CALL(
      *mock_permission_controller,
      GetPermissionResultForOriginWithoutContext(testing::_, testing::_))
      .WillRepeatedly(testing::Return(permission_result_mock_return));
  browser_context()->SetPermissionControllerForTesting(
      std::move(mock_permission_controller));

  PushMessagingServiceImpl* push_service =
      PushMessagingServiceFactory::GetForProfile(profile());
  ASSERT_TRUE(push_service);
  const GURL extension_origin =
      Extension::GetBaseURLFromExtensionId(extension->id());

  // All extension origins (worker or non-worker) can always use the Push API if
  // they set userVisibleOnly:true on push subscription.
  EXPECT_EQ(
      blink::mojom::PermissionStatus::GRANTED,
      push_service->GetPermissionStatus(extension_origin, /*user_visible=*/
                                        true));

  // Extension origins with workers can set userVisible:false when subscribing
  // and still use the Push API if they don't intend to show notifications
  // (if they try to show notifications then the permission is still enforced).
  if (worker_extension) {
    EXPECT_EQ(
        blink::mojom::PermissionStatus::GRANTED,
        push_service->GetPermissionStatus(extension_origin, /*user_visible=*/
                                          false));
  } else {
    // Extension origins that are not worker based are not allowed to use the
    // Push API if they set userVisibleOnly:false on subscription.
    EXPECT_EQ(
        blink::mojom::PermissionStatus::DENIED,
        push_service->GetPermissionStatus(extension_origin, /*user_visible=*/
                                          false));
  }
}

// Tests that `userVisibleOnly: false` is correctly persisted and returned
// for worker-based extensions.
TEST_P(ExtensionsPushMessagingServiceTest,
       GetSubscriptionPersistsUserVisibleOnlyFalse) {
  ContextType extension_context_type = GetParam();
  if (extension_context_type != ContextType::kServiceWorker) {
    return;  // Only supported for service worker extensions
  }

  // Load a manifest V3 service worker extension.
  TestExtensionDir test_dir;
  constexpr char kManifest[] =
      R"({
         "name": "Test Extension",
         "manifest_version": 3,
         "version": "0.1",
         "background": {"service_worker": "background.js"}
       })";
  test_dir.WriteManifest(kManifest);
  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "");
  ChromeTestExtensionLoader loader(profile());
  scoped_refptr<const Extension> extension =
      loader.LoadExtension(test_dir.UnpackedPath());
  ASSERT_TRUE(extension);

  PushMessagingServiceImpl* push_service =
      PushMessagingServiceFactory::GetForProfile(profile());
  ASSERT_TRUE(push_service);
  const GURL extension_origin =
      Extension::GetBaseURLFromExtensionId(extension->id());

  std::string subscription_id;
  base::RunLoop subscribe_run_loop;

  // Create options with `user_visible_only` `false`.
  auto options = blink::mojom::PushSubscriptionOptions::New();
  options->user_visible_only = false;
  options->application_server_key =
      std::vector<uint8_t>(std::begin(kTestSenderId), std::end(kTestSenderId));

  // Subscribe from worker.
  push_service->SubscribeFromWorker(
      extension_origin, kTestServiceWorkerId, /*render_process_id=*/-1,
      std::move(options),
      base::BindLambdaForTesting(
          [&](const std::string& registration_id, const GURL& endpoint,
              const std::optional<base::Time>& expiration_time,
              const std::vector<uint8_t>& p256dh,
              const std::vector<uint8_t>& auth,
              blink::mojom::PushRegistrationStatus status) {
            EXPECT_EQ(
                blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE,
                status);
            subscription_id = registration_id;
            subscribe_run_loop.Quit();
          }));

  {
    SCOPED_TRACE("Waiting for subscription from worker to complete");
    subscribe_run_loop.Run();
  }
  ASSERT_FALSE(subscription_id.empty());

  bool out_is_valid = false;
  bool out_user_visible_only = true;

  // Retrieve subscription info and verify that `user_visible_only` is `false`.
  base::RunLoop get_subscription_info_run_loop;
  push_service->GetSubscriptionInfo(
      extension_origin, kTestServiceWorkerId, std::string(kTestSenderId),
      subscription_id,
      base::BindLambdaForTesting(
          [&](bool is_valid, bool user_visible_only, const GURL& endpoint,
              const std::optional<base::Time>& expiration_time,
              const std::vector<uint8_t>& p256dh,
              const std::vector<uint8_t>& auth) {
            out_is_valid = is_valid;
            out_user_visible_only = user_visible_only;
            get_subscription_info_run_loop.Quit();
          }));

  {
    SCOPED_TRACE("Waiting for retrieval of subscription info to complete");
    get_subscription_info_run_loop.Run();
  }

  EXPECT_TRUE(out_is_valid);
  EXPECT_FALSE(out_user_visible_only);
}

// Android only supports manifest V3 with service worker.
#if !BUILDFLAG(IS_ANDROID)
INSTANTIATE_TEST_SUITE_P(
    NonWorkerExtension,
    ExtensionsPushMessagingServiceTest,
    testing::ValuesIn({ContextType::kEventPage,
                       ContextType::kPersistentBackground}));
#endif
INSTANTIATE_TEST_SUITE_P(WorkerBasedExtension,
                         ExtensionsPushMessagingServiceTest,
                         testing::Values(ContextType::kServiceWorker));

}  // namespace extensions

#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)

#if BUILDFLAG(IS_ANDROID)
class FCMRevocationTest : public PushMessagingServiceTest {
 public:
  FCMRevocationTest() {
    PushMessagingServiceImpl::RegisterPrefs(prefs_.registry());
  }

  ~FCMRevocationTest() override = default;

  GURL GetUrl() { return origin_; }

  url::Origin GetOrigin() { return url::Origin::Create(origin_); }

  PrefService* pref() { return &prefs_; }

  void SetPermission(const GURL& origin,
                     ContentSetting value,
                     TestingProfile* profile) {
    HostContentSettingsMap* host_content_settings_map =
        HostContentSettingsMapFactory::GetForProfile(profile);
    host_content_settings_map->SetContentSettingDefaultScope(
        origin, origin, ContentSettingsType::NOTIFICATIONS, value);
  }

 private:
  GURL origin_ = GURL("https://example.com");
  TestingPrefServiceSimple prefs_;
};

TEST_F(FCMRevocationTest, ResetPrefs) {
  content::PermissionController* permission_controller =
      profile()->GetPermissionController();

  const auto notification_permission_descriptor = content::
      PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
          blink::PermissionType::NOTIFICATIONS);
  content::PermissionResult result =
      permission_controller->GetPermissionResultForOriginWithoutContext(
          notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK);

  SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile());

  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);

  const char kNotificationsPermissionRevocationGracePeriodDate[] =
      "notifications_permission_revocation_grace_period";

  // Just random value to make sure it is reset.
  base::Time time = base::Time::FromTimeT(100);

  pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time);

  PushMessagingServiceImpl::RevokePermissionIfPossible(
      GetUrl(), /*app_level_notifications_enabled=*/true, pref(), profile());

  // Time is reset.
  base::Time grace_period_date =
      pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate);
  EXPECT_EQ(grace_period_date, base::Time());
  EXPECT_NE(grace_period_date, time);

  // Permission is not reset.
  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);
}

// This test verifies that if the grace period is not started, and there is no
// app-level Notifications permissions, we init the grace period prefs without
// revoking permissions.
TEST_F(FCMRevocationTest, NoAppLevelPermissionInitGracePeriodPrefsTest) {
  content::PermissionController* permission_controller =
      profile()->GetPermissionController();

  const auto notification_permission_descriptor = content::
      PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
          blink::PermissionType::NOTIFICATIONS);
  content::PermissionResult result =
      permission_controller->GetPermissionResultForOriginWithoutContext(
          notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK);

  SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile());

  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);

  const char kNotificationsPermissionRevocationGracePeriodDate[] =
      "notifications_permission_revocation_grace_period";

  // The grace period is not initialized.
  EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            base::Time());

  PushMessagingServiceImpl::RevokePermissionIfPossible(
      GetUrl(), /*app_level_notifications_enabled=*/false, pref(), profile());

  // The grace period is initialized with non-default time value.
  EXPECT_NE(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            base::Time());

  // Permission is still granted.
  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);
}

// This test verifies that if the grace period is over and there is no app-level
// Notifications permissions, site-level Notifications permission will be
// revoked.
TEST_F(FCMRevocationTest, NoAppLevelPermissionRevocationTest) {
  content::PermissionController* permission_controller =
      profile()->GetPermissionController();
  const auto notification_permission_descriptor = content::
      PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
          blink::PermissionType::NOTIFICATIONS);
  content::PermissionResult result =
      permission_controller->GetPermissionResultForOriginWithoutContext(
          notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK);

  SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile());

  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);

  const char kNotificationsPermissionRevocationGracePeriodDate[] =
      "notifications_permission_revocation_grace_period";

  // The grace period is not initialized.
  EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            base::Time());

  // Init `time` with 4 days old time value.
  const base::Time time = base::Time::FromDeltaSinceWindowsEpoch(
      base::Time::Now() -
      base::Time::FromDeltaSinceWindowsEpoch(base::Days(4)));

  // Init the grace period date with a value that is older than 3 days (the
  // default grace period).
  pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time);

  PushMessagingServiceImpl::RevokePermissionIfPossible(
      GetUrl(), /*app_level_notifications_enabled=*/false, pref(), profile());

  EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            time);

  // Permission is revoked.
  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK);
}

// This test verifies that if the grace period is not over and there is no
// app-level Notifications permissions, site-level Notifications permission will
// not be revoked.
TEST_F(FCMRevocationTest, NoAppLevelPermissionIgnoreTest) {
  content::PermissionController* permission_controller =
      profile()->GetPermissionController();
  const auto notification_permission_descriptor = content::
      PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
          blink::PermissionType::NOTIFICATIONS);
  content::PermissionResult result =
      permission_controller->GetPermissionResultForOriginWithoutContext(
          notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK);

  SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile());

  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);

  const char kNotificationsPermissionRevocationGracePeriodDate[] =
      "notifications_permission_revocation_grace_period";

  // The grace period is not initialized.
  EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            base::Time());

  // Init `time` with 2 days old time value.
  const base::Time time = base::Time::FromDeltaSinceWindowsEpoch(
      base::Time::Now() -
      base::Time::FromDeltaSinceWindowsEpoch(base::Days(2)));
  // Init the grace period date with a value that is fewer than 3 days (the
  // default grace period).
  pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time);

  PushMessagingServiceImpl::RevokePermissionIfPossible(
      GetUrl(), /*app_level_notifications_enabled=*/false, pref(), profile());

  EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            time);

  // Permission is revoked.
  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);
}

// This test verifies that if the grace period is not over and there is
// app-level Notifications permissions, the grace period reset will be tracked.
TEST_F(FCMRevocationTest, ResetAndRecordGracePeriodTest) {
  content::PermissionController* permission_controller =
      profile()->GetPermissionController();
  const auto notification_permission_descriptor = content::
      PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
          blink::PermissionType::NOTIFICATIONS);
  content::PermissionResult result =
      permission_controller->GetPermissionResultForOriginWithoutContext(
          notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK);

  SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile());

  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);

  const char kNotificationsPermissionRevocationGracePeriodDate[] =
      "notifications_permission_revocation_grace_period";

  // The grace period is not initialized.
  EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            base::Time());

  // Init `time` with 2 days old time value.
  const base::Time time = base::Time::FromDeltaSinceWindowsEpoch(
      base::Time::Now() -
      base::Time::FromDeltaSinceWindowsEpoch(base::Days(2)));
  // Init the grace period date with a value that is fewer than 3 days (the
  // default grace period).
  pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time);

  PushMessagingServiceImpl::RevokePermissionIfPossible(
      GetUrl(), /*app_level_notifications_enabled=*/true, pref(), profile());

  EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate),
            base::Time());

  // Permission is revoked.
  result = permission_controller->GetPermissionResultForOriginWithoutContext(
      notification_permission_descriptor, GetOrigin());
  EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED);
}

#endif
