// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/interstitials/enterprise_util.h"

#include <string>

#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h"
#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h"
#include "chrome/browser/enterprise/connectors/test/deep_scanning_test_utils.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager_factory.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/enterprise/connectors/core/reporting_test_utils.h"
#include "components/policy/core/common/cloud/dm_token.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h"
#include "components/safe_browsing/core/browser/referrer_chain_provider.h"
#include "components/safe_browsing/core/common/features.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/fake_service_worker_context.h"
#include "content/public/test/test_web_contents_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
#include "chrome/browser/safe_browsing/test_extension_event_observer.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(SAFE_BROWSING_AVAILABLE)

#if BUILDFLAG(IS_ANDROID)
#include "components/enterprise/connectors/core/features.h"
#endif

using ::testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgPointee;

class MockSafeBrowsingNavigationObserverManager
    : public safe_browsing::SafeBrowsingNavigationObserverManager {
 public:
  explicit MockSafeBrowsingNavigationObserverManager(
      PrefService* pref_service,
      content::ServiceWorkerContext* context)
      : safe_browsing::SafeBrowsingNavigationObserverManager(pref_service,
                                                             context),
        notification_context_for_removal_(context) {}

  ~MockSafeBrowsingNavigationObserverManager() override {
    if (notification_context_for_removal_) {
      notification_context_for_removal_->RemoveObserver(this);
      ui::Clipboard::GetForCurrentThread()->RemoveObserver(this);
    }
  }

  MOCK_METHOD4(IdentifyReferrerChainByEventURL,
               safe_browsing::ReferrerChainProvider::AttributionResult(
                   const GURL& event_url,
                   SessionID event_tab_id,
                   int user_gesture_count_limit,
                   safe_browsing::ReferrerChain* out_referrer_chain));

  MOCK_METHOD3(
      IdentifyReferrerChainByPendingEventURL,
      AttributionResult(const GURL& event_url,
                        int user_gesture_count_limit,
                        safe_browsing::ReferrerChain* out_referrer_chain));

 private:
  raw_ptr<content::ServiceWorkerContext> notification_context_for_removal_;
};

class InterstitialEnterpriseUtilTest : public testing::Test {
 public:
  InterstitialEnterpriseUtilTest()
      : profile_manager_(TestingBrowserProcess::GetGlobal()) {
    EXPECT_TRUE(profile_manager_.SetUp());
  }

  std::unique_ptr<KeyedService> BuildMockNavigationObserverManager(
      content::BrowserContext* context) {
    Profile* profile = Profile::FromBrowserContext(context);
    PrefService* pref_service = profile->GetPrefs();
    CHECK(pref_service);
    content::ServiceWorkerContext* service_worker_context =
        profile->GetDefaultStoragePartition()->GetServiceWorkerContext();
    CHECK(service_worker_context);
    auto mock_manager =
        std::make_unique<MockSafeBrowsingNavigationObserverManager>(
            pref_service, service_worker_context);
    navigation_observer_manager_ = mock_manager.get();
    return mock_manager;
  }

  void SetUp() override {
    policy::SetDMTokenForTesting(
        policy::DMToken::CreateValidToken("fake-token"));
  }

  void EnableReportingPolicy(Profile* profile) {
#if BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(SAFE_BROWSING_AVAILABLE)
    extensions::SafeBrowsingPrivateEventRouterFactory::GetInstance()
        ->SetTestingFactory(
            profile, base::BindRepeating(
                         &safe_browsing::BuildSafeBrowsingPrivateEventRouter));
#endif  // BUILDFLAG(ENABLE_EXTENSIONS) && BUILDFLAG(SAFE_BROWSING_AVAILABLE)
    enterprise_connectors::RealtimeReportingClientFactory::GetInstance()
        ->SetTestingFactory(
            profile,
            base::BindRepeating(
                &enterprise_connectors::test::BuildRealtimeReportingClient));
    enterprise_connectors::test::SetOnSecurityEventReporting(
        profile->GetPrefs(), /*enabled=*/true, /*enabled_event_names=*/{},
        /*enabled_opt_in_events=*/{});
    auto* navigation_observer_manager_factory = safe_browsing::
        SafeBrowsingNavigationObserverManagerFactory::GetInstance();
    navigation_observer_manager_factory->SetTestingFactory(
        profile,
        base::BindRepeating(
            &InterstitialEnterpriseUtilTest::BuildMockNavigationObserverManager,
            base::Unretained(this)));

    // Set a mock cloud policy client in the router.
    client_ = std::make_unique<policy::MockCloudPolicyClient>();
    client_->SetDMToken("fake-token");
    enterprise_connectors::RealtimeReportingClientFactory::GetForProfile(
        profile)
        ->SetBrowserCloudPolicyClientForTesting(client_.get());
  }

  void ValidateReferrerChain(const base::DictValue& report_dict,
                             std::string_view event_name) {
    const base::ListValue* events_list = report_dict.FindList("events");
    ASSERT_NE(events_list, nullptr);
    ASSERT_EQ(events_list->size(), 1u);
    const base::DictValue* event_dict = (*events_list)[0].GetIfDict();
    ASSERT_NE(event_dict, nullptr);
    const base::DictValue* intertitial_event = event_dict->FindDict(event_name);
    ASSERT_NE(intertitial_event, nullptr);
    const base::ListValue* referrer_chain =
        intertitial_event->FindList("referrers");
    EXPECT_EQ(referrer_chain->size(), 1u);
  }

  void ValidateReferrerChainForInterstitialEvent(
      const ::chrome::cros::reporting::proto::UploadEventsRequest& request) {
    ASSERT_EQ(1, request.events_size());
    ASSERT_TRUE(request.events().Get(0).has_interstitial_event());
    auto event = request.events().Get(0).interstitial_event();
    ASSERT_EQ(event.referrers_size(), 1);
  }

  void ValidateReferrerChainForUrlFilteringEvent(
      const ::chrome::cros::reporting::proto::UploadEventsRequest& request) {
    ASSERT_EQ(1, request.events_size());
    ASSERT_TRUE(request.events().Get(0).has_url_filtering_interstitial_event());
    auto event = request.events().Get(0).url_filtering_interstitial_event();
    ASSERT_EQ(event.referrers_size(), 1);
  }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<policy::MockCloudPolicyClient> client_;
  TestingProfileManager profile_manager_;
  content::TestWebContentsFactory web_contents_factory_;
  raw_ptr<MockSafeBrowsingNavigationObserverManager>
      navigation_observer_manager_ = nullptr;
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(InterstitialEnterpriseUtilTest, RouterEventDisabledInIncognitoMode) {
  scoped_feature_list_.InitAndEnableFeature(
      safe_browsing::kEnhancedFieldsForSecOps);
  Profile* incognito_profile =
      profile_manager_.CreateTestingProfile("testing_profile")
          ->GetPrimaryOTRProfile(
              /*create_if_needed=*/true);
  EnableReportingPolicy(incognito_profile);

  EXPECT_CALL(*client_, UploadSecurityEvent).Times(0);

  MaybeTriggerSecurityInterstitialShownEvent(
      web_contents_factory_.CreateWebContents(incognito_profile),
      GURL("https://phishing.com/"), "reason",
      /*net_error_code=*/0);
}

TEST_F(InterstitialEnterpriseUtilTest,
       SecurityInterstitialShownEventSentInGuestMode) {
  scoped_feature_list_.InitAndEnableFeature(
      safe_browsing::kEnhancedFieldsForSecOps);
  Profile* guest_profile =
      profile_manager_.CreateGuestProfile()->GetPrimaryOTRProfile(
          /*create_if_needed=*/true);
  EnableReportingPolicy(guest_profile);

  ASSERT_TRUE(safe_browsing::SafeBrowsingNavigationObserverManagerFactory::
                  GetForBrowserContext(guest_profile));
  safe_browsing::ReferrerChain expected_referrer_chain;
  expected_referrer_chain.Add(
      enterprise_connectors::test::MakeReferrerChainEntry());
  EXPECT_CALL(
      *navigation_observer_manager_,
      IdentifyReferrerChainByPendingEventURL(GURL("https://phishing.com/"),
                                             /*user_gesture_count_limit=*/5, _))
      .WillOnce(DoAll(SetArgPointee<2>(expected_referrer_chain),
                      Return(safe_browsing::ReferrerChainProvider::SUCCESS)));
  base::RunLoop run_loop;
  ::chrome::cros::reporting::proto::UploadEventsRequest event_request;

  EXPECT_CALL(*client_, UploadSecurityEvent)
      .Times(1)
      .WillOnce(
          [&](bool include_device_info,
              ::chrome::cros::reporting::proto::UploadEventsRequest&& request,
              policy::CloudPolicyClient::ResultCallback callback) {
            event_request = std::move(request);
            run_loop.Quit();
          });

  MaybeTriggerSecurityInterstitialShownEvent(
      web_contents_factory_.CreateWebContents(guest_profile),
      GURL("https://phishing.com/"), "reason",
      /*net_error_code=*/0);
  run_loop.Run();

  ValidateReferrerChainForInterstitialEvent(event_request);
}

TEST_F(InterstitialEnterpriseUtilTest,
       SecurityInterstitialProceededEventSentInGuestMode) {
  scoped_feature_list_.InitAndEnableFeature(
      safe_browsing::kEnhancedFieldsForSecOps);
  Profile* guest_profile =
      profile_manager_.CreateGuestProfile()->GetPrimaryOTRProfile(
          /*create_if_needed=*/true);
  EnableReportingPolicy(guest_profile);

  ASSERT_TRUE(safe_browsing::SafeBrowsingNavigationObserverManagerFactory::
                  GetForBrowserContext(guest_profile));
  safe_browsing::ReferrerChain expected_referrer_chain;
  expected_referrer_chain.Add(
      enterprise_connectors::test::MakeReferrerChainEntry());
  EXPECT_CALL(
      *navigation_observer_manager_,
      IdentifyReferrerChainByPendingEventURL(GURL("https://phishing.com/"),
                                             /*user_gesture_count_limit=*/5, _))
      .WillOnce(DoAll(SetArgPointee<2>(expected_referrer_chain),
                      Return(safe_browsing::ReferrerChainProvider::SUCCESS)));
  base::RunLoop run_loop;
  ::chrome::cros::reporting::proto::UploadEventsRequest event_request;

  EXPECT_CALL(*client_, UploadSecurityEvent)
      .Times(1)
      .WillOnce(
          [&](bool include_device_info,
              ::chrome::cros::reporting::proto::UploadEventsRequest&& request,
              policy::CloudPolicyClient::ResultCallback callback) {
            event_request = std::move(request);
            run_loop.Quit();
          });

  MaybeTriggerSecurityInterstitialProceededEvent(
      web_contents_factory_.CreateWebContents(guest_profile),
      GURL("https://phishing.com/"), "reason",
      /*net_error_code=*/0);
  run_loop.Run();

  ValidateReferrerChainForInterstitialEvent(event_request);
}

TEST_F(InterstitialEnterpriseUtilTest,
       UrlFilteringInterstitialEventSentInGuestMode) {
  scoped_feature_list_.InitAndEnableFeature(
      safe_browsing::kEnhancedFieldsForSecOps);
  Profile* guest_profile =
      profile_manager_.CreateGuestProfile()->GetPrimaryOTRProfile(
          /*create_if_needed=*/true);
  EnableReportingPolicy(guest_profile);

  ASSERT_TRUE(safe_browsing::SafeBrowsingNavigationObserverManagerFactory::
                  GetForBrowserContext(guest_profile));
  safe_browsing::RTLookupResponse response;
  auto* threat_info = response.add_threat_info();
  threat_info->set_verdict_type(
      safe_browsing::RTLookupResponse::ThreatInfo::DANGEROUS);
  auto* matched_url_navigation_rule =
      threat_info->mutable_matched_url_navigation_rule();
  matched_url_navigation_rule->set_rule_id("123");
  matched_url_navigation_rule->set_rule_name("test rule name");
  matched_url_navigation_rule->set_matched_url_category("test rule category");
  safe_browsing::ReferrerChain expected_referrer_chain;
  expected_referrer_chain.Add(
      enterprise_connectors::test::MakeReferrerChainEntry());
  EXPECT_CALL(
      *navigation_observer_manager_,
      IdentifyReferrerChainByPendingEventURL(GURL("https://phishing.com/"),
                                             /*user_gesture_count_limit=*/5, _))
      .WillOnce(DoAll(SetArgPointee<2>(expected_referrer_chain),
                      Return(safe_browsing::ReferrerChainProvider::SUCCESS)));
  base::RunLoop run_loop;
  ::chrome::cros::reporting::proto::UploadEventsRequest event_request;

  EXPECT_CALL(*client_, UploadSecurityEvent)
      .Times(1)
      .WillOnce(
          [&](bool include_device_info,
              ::chrome::cros::reporting::proto::UploadEventsRequest&& request,
              policy::CloudPolicyClient::ResultCallback callback) {
            event_request = std::move(request);
            run_loop.Quit();
          });

  MaybeTriggerUrlFilteringInterstitialEvent(
      web_contents_factory_.CreateWebContents(guest_profile),
      GURL("https://phishing.com/"), "ENTERPRISE_WARNED_SEEN", response);
  run_loop.Run();

  ValidateReferrerChainForUrlFilteringEvent(event_request);
}

TEST_F(InterstitialEnterpriseUtilTest, ReferrerChainFallsbackToEventUrl) {
  scoped_feature_list_.InitAndEnableFeature(
      safe_browsing::kEnhancedFieldsForSecOps);
  Profile* guest_profile =
      profile_manager_.CreateGuestProfile()->GetPrimaryOTRProfile(
          /*create_if_needed=*/true);
  EnableReportingPolicy(guest_profile);

  ASSERT_TRUE(safe_browsing::SafeBrowsingNavigationObserverManagerFactory::
                  GetForBrowserContext(guest_profile));
  safe_browsing::RTLookupResponse response;
  auto* threat_info = response.add_threat_info();
  threat_info->set_verdict_type(
      safe_browsing::RTLookupResponse::ThreatInfo::DANGEROUS);
  auto* matched_url_navigation_rule =
      threat_info->mutable_matched_url_navigation_rule();
  matched_url_navigation_rule->set_rule_id("123");
  matched_url_navigation_rule->set_rule_name("test rule name");
  matched_url_navigation_rule->set_matched_url_category("test rule category");
  safe_browsing::ReferrerChain expected_referrer_chain;
  expected_referrer_chain.Add(
      enterprise_connectors::test::MakeReferrerChainEntry());
  EXPECT_CALL(
      *navigation_observer_manager_,
      IdentifyReferrerChainByPendingEventURL(GURL("https://phishing.com/"),
                                             /*user_gesture_count_limit=*/5, _))
      .WillOnce(Return(
          safe_browsing::ReferrerChainProvider::NAVIGATION_EVENT_NOT_FOUND));
  EXPECT_CALL(
      *navigation_observer_manager_,
      IdentifyReferrerChainByEventURL(GURL("https://phishing.com/"), _,
                                      /*user_gesture_count_limit=*/5, _))
      .WillOnce(DoAll(SetArgPointee<3>(expected_referrer_chain),
                      Return(safe_browsing::ReferrerChainProvider::SUCCESS)));
  base::RunLoop run_loop;
  ::chrome::cros::reporting::proto::UploadEventsRequest event_request;

  EXPECT_CALL(*client_, UploadSecurityEvent)
      .Times(1)
      .WillOnce(
          [&](bool include_device_info,
              ::chrome::cros::reporting::proto::UploadEventsRequest&& request,
              policy::CloudPolicyClient::ResultCallback callback) {
            event_request = std::move(request);
            run_loop.Quit();
          });

  MaybeTriggerUrlFilteringInterstitialEvent(
      web_contents_factory_.CreateWebContents(guest_profile),
      GURL("https://phishing.com/"), "ENTERPRISE_WARNED_SEEN", response);
  run_loop.Run();

  ValidateReferrerChainForUrlFilteringEvent(event_request);
}
