// Copyright 2021 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/net/stub_resolver_config_reader.h"

#include <string>

#include "base/enterprise_util.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/secure_dns_config.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "net/base/features.h"
#include "net/dns/public/secure_dns_mode.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/net/secure_dns_manager.h"
#endif

// TODO(ericorth@chromium.org): Consider validating that the expected
// configuration makes it all the way to the net::HostResolverManager in the
// network service, rather than just testing StubResolverConfigReader output.

namespace {

const std::string kDnsOverHttpsTemplatesPrefName =
    prefs::kDnsOverHttpsTemplates;

class StubResolverConfigReaderBrowsertest
    : public InProcessBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  StubResolverConfigReaderBrowsertest() {
    scoped_feature_list_.InitWithFeatureState(net::features::kAsyncDns,
                                              GetParam());
  }
  ~StubResolverConfigReaderBrowsertest() override = default;

  void SetUpInProcessBrowserTestFixture() override {
    // Normal boilerplate to setup a MockConfigurationPolicyProvider.
    policy_provider_.SetDefaultReturns(
        /*is_initialization_complete_return=*/true,
        /*is_first_policy_load_complete_return=*/true);
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
        &policy_provider_);
  }

  void SetUpOnMainThread() override {
    // Set the mocked policy provider to act as if no policies are in use by
    // updating to the initial still-empty `policy_map_`.
    ASSERT_TRUE(policy_map_.empty());
    policy_provider_.UpdateChromePolicy(policy_map_);

    config_reader_ = SystemNetworkContextManager::GetStubResolverConfigReader();

    // Only affects future parental controls checks, but that is all that
    // matters here because these tests only deal with checking fresh config
    // reads, not what has been sent to Network Service.
    //
    // TODO(ericorth@chromium.org): If validation of network service state is
    // ever added, need to ensure the result of this override gets sent first.
    config_reader_->OverrideParentalControlsForTesting(
        /*parental_controls_override=*/false);
  }

 protected:
  void SetSecureDnsModePolicy(std::string mode_str) {
    policy_map_.Set(
        policy::key::kDnsOverHttpsMode, policy::POLICY_LEVEL_MANDATORY,
        policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
        base::Value(std::move(mode_str)),
        /*external_data_fetcher=*/nullptr);
    policy_provider_.UpdateChromePolicy(policy_map_);
  }

  void SetDohTemplatesPolicy(std::string teplates_str) {
    policy_map_.Set(
        policy::key::kDnsOverHttpsTemplates, policy::POLICY_LEVEL_MANDATORY,
        policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM,
        base::Value(std::move(teplates_str)),
        /*external_data_fetcher=*/nullptr);
    policy_provider_.UpdateChromePolicy(policy_map_);
  }

  policy::PolicyMap policy_map_;
  testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;

  raw_ptr<StubResolverConfigReader, DanglingUntriaged> config_reader_;

  base::test::ScopedFeatureList scoped_feature_list_;
};

// Set various DoH modes and DoH template strings and make sure the settings are
// respected.
IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, ConfigFromPrefs) {
  bool async_dns_feature_enabled = GetParam();

  // Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  // TODO(crbug.com/40229843): What is the correct function to use here?
  EXPECT_FALSE(base::win::IsEnrolledToDomain());
#endif

  std::string good_post_template = "https://foo.test/";
  std::string good_get_template = "https://bar.test/dns-query{?dns}";
  std::string bad_template = "dns-query{?dns}";
  std::string good_then_bad_template = good_get_template + " " + bad_template;
  std::string bad_then_good_template = bad_template + " " + good_get_template;
  std::string multiple_good_templates =
      "  " + good_get_template + "   " + good_post_template + "  ";

  PrefService* pref_service_for_user_settings =
      g_browser_process->local_state();

#if BUILDFLAG(IS_CHROMEOS)
  // On ChromeOS, the local_state is shared between all users so the user-set
  // pref is stored in the profile's pref service.
  pref_service_for_user_settings = browser()->GetProfile()->GetPrefs();
#endif  // BUILDFLAG(IS_CHROMEOS)

  pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode,
                                            SecureDnsConfig::kModeSecure);
  pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName,
                                            bad_template);
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kSecure, secure_dns_config.mode());
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());

  pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName,
                                            good_post_template);
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kSecure, secure_dns_config.mode());
  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(good_post_template),
            secure_dns_config.doh_servers());

  pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode,
                                            SecureDnsConfig::kModeAutomatic);
  pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName,
                                            bad_template);
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode());
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());

  pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName,
                                            good_then_bad_template);
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode());
  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(good_get_template),
            secure_dns_config.doh_servers());

  pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName,
                                            bad_then_good_template);
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode());
  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(good_get_template),
            secure_dns_config.doh_servers());

  pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName,
                                            multiple_good_templates);
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode());
  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(multiple_good_templates),
            secure_dns_config.doh_servers());

  pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode,
                                            SecureDnsConfig::kModeOff);
  pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName,
                                            good_get_template);
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kOff, secure_dns_config.mode());
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());

  pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode,
                                            "no_match");
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kOff, secure_dns_config.mode());
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());

  // Test case with policy BuiltInDnsClientEnabled enabled. The DoH fields
  // should be unaffected.
  // The `prefs::kBuiltInDnsClientEnabled` pref is stored on local_state on all
  // platforms (including Chrome OS).
  g_browser_process->local_state()->Set(
      prefs::kBuiltInDnsClientEnabled, base::Value(!async_dns_feature_enabled));
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      false /* force_check_parental_controls_for_automatic_mode */);
  EXPECT_EQ(!async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(net::SecureDnsMode::kOff, secure_dns_config.mode());
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
}

IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest,
                       DefaultNonSetPolicies) {
  bool async_dns_feature_enabled = GetParam();
// Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  EXPECT_FALSE(base::IsEnterpriseDevice());
#endif

  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/false);
  EXPECT_EQ(async_dns_feature_enabled,
            config_reader_->GetInsecureStubResolverEnabled());
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kAutomatic);
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
}

// ChromeOS includes its own special functionality to set default policies if
// any policies are set.  This function is not declared and cannot be invoked
// in non-CrOS builds. Expect these enterprise user defaults to disable DoH.
#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, SpecialPolicies) {
  // Applies the special ChromeOS defaults to `policy_map_`.
  policy::SetEnterpriseUsersDefaults(&policy_map_);
  // Send the PolicyMap to the mock policy provider.
  policy_provider_.UpdateChromePolicy(policy_map_);
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/false);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff);
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
}
#endif  // BUILDFLAG(IS_CHROMEOS)

IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest,
                       DisableDohByPolicy) {
// Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  EXPECT_FALSE(base::IsEnterpriseDevice());
#endif

  SetSecureDnsModePolicy("off");
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/false);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff);
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
}

IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest,
                       AutomaticModeByPolicy) {
// Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  EXPECT_FALSE(base::IsEnterpriseDevice());
#endif

  SetSecureDnsModePolicy("automatic");
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/false);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kAutomatic);
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
}

IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest,
                       SecureModeByPolicy) {
// Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  EXPECT_FALSE(base::IsEnterpriseDevice());
#endif

  SetSecureDnsModePolicy("secure");
  SetDohTemplatesPolicy("https://doh.test/");
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/false);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kSecure);
  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString("https://doh.test/"),
            secure_dns_config.doh_servers());
}

IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest,
                       InvalidTemplatePolicy) {
// Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  EXPECT_FALSE(base::IsEnterpriseDevice());
#endif

  SetSecureDnsModePolicy("secure");
  SetDohTemplatesPolicy("invalid template");
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/false);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kSecure);
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
  // Deterministic regression test for flaky failures seen in
  // https://crbug.com/40840601. This induces a DNS resolution while in secure
  // mode with zero DoH server templates to use.
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("foo.example", "/")));
}

IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, InvalidModePolicy) {
// Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  EXPECT_FALSE(base::IsEnterpriseDevice());
#endif

  SetSecureDnsModePolicy("invalid");
  SetDohTemplatesPolicy("https://doh.test/");
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/false);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff);
  // Expect empty templates if mode policy is invalid.
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
}

// Test that parental controls detection interacts correctly with prefs and
// policies.
IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest,
                       ConfigFromParentalControls) {
// Mark as not enterprise managed.
#if BUILDFLAG(IS_WIN)
  base::win::ScopedDomainStateForTesting scoped_domain(false);
  EXPECT_FALSE(base::IsEnterpriseDevice());
#endif

  config_reader_->OverrideParentalControlsForTesting(
      /*parental_controls_override=*/true);

  // Parental controls takes precedence over regular prefs.
  PrefService* local_state = g_browser_process->local_state();
  local_state->SetString(prefs::kDnsOverHttpsMode,
                         SecureDnsConfig::kModeAutomatic);
  SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/true);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff);
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());

  // Policy takes precedence over parental controls.
  SetSecureDnsModePolicy("automatic");
  SetDohTemplatesPolicy("");
  secure_dns_config = config_reader_->GetSecureDnsConfiguration(
      /*force_check_parental_controls_for_automatic_mode=*/true);
  EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kAutomatic);
  EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty());
}

#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest,
                       AsyncDnsDisabledWhenZTDNSEnabled) {
  // This test focuses on the interaction of ZTDNS with the AsyncDns feature.
  // The StubResolverConfigReader constructor, which runs during
  // SetUpOnMainThread, sets the *default* value of
  // prefs::kBuiltInDnsClientEnabled based on ShouldEnableAsyncDns(). To test
  // the effect of ZTDNS, we set the override *before* a relevant
  // StubResolverConfigReader instance evaluates this default. We achieve this
  // by creating a new instance after setting the override.

  // Simulate ZTDNS is ON
  StubResolverConfigReader::SetZTDNSEnabledForTesting(true);

  // Create a new StubResolverConfigReader instance. Its constructor will use
  // the ZTDNS override when calling ShouldEnableAsyncDns() to set the default
  // value for prefs::kBuiltInDnsClientEnabled in the local_state's
  // PrefRegistry. We pass 'true' for set_up_pref_defaults to ensure this
  // happens.
  StubResolverConfigReader test_config_reader(g_browser_process->local_state(),
                                              true /* set_up_pref_defaults */);

  PrefService* local_state = g_browser_process->local_state();

  // Case 1: AsyncDns feature is ON.
  // With ZTDNS enabled (mocked), ShouldEnableAsyncDns() should return false.
  // Therefore, the default for kBuiltInDnsClientEnabled should be false.
  // Case 2: AsyncDns feature is OFF.
  // ShouldEnableAsyncDns() should return false (regardless of ZTDNS).
  // Therefore, the default for kBuiltInDnsClientEnabled should be false.
  EXPECT_FALSE(local_state->GetBoolean(prefs::kBuiltInDnsClientEnabled))
      << "kBuiltInDnsClientEnabled should default to false when AsyncDns "
         "feature is off.";
  EXPECT_FALSE(test_config_reader.GetInsecureStubResolverEnabled())
      << "GetInsecureStubResolverEnabled should be false when AsyncDns "
         "feature is off.";

  // Sanity check SecureDnsConfig (DoH settings should be unaffected by this
  // specific test, assuming default DoH mode 'automatic' and no templates,
  // leading to 'off' effectively unless other conditions for 'automatic' are
  // met, which are not the focus here). This part primarily ensures
  // GetSecureDnsConfiguration can be called without crashing.
  SecureDnsConfig secure_dns_config =
      test_config_reader.GetSecureDnsConfiguration(
          false /* force_check_parental_controls_for_automatic_mode */);
  // Default DoH mode is kAutomatic, which without explicit templates often
  // resolves to effectively off for the purpose of doh_servers(), unless probes
  // succeed. The exact mode isn't the primary assert here, but rather that
  // GetInsecureStubResolverEnabled is correct. Depending on default DoH
  // settings, mode might be kAutomatic or kOff.
  EXPECT_TRUE(secure_dns_config.mode() == net::SecureDnsMode::kAutomatic ||
              secure_dns_config.mode() == net::SecureDnsMode::kOff);
}
#endif

INSTANTIATE_TEST_SUITE_P(All,
                         StubResolverConfigReaderBrowsertest,
                         ::testing::Bool());

}  // namespace
