blob: c992dfe0c8b5a6f2729c7c53d6b1762972146789 [file] [log] [blame]
// 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/ash/net/secure_dns_manager.h"
#include "ash/constants/ash_pref_names.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "chrome/browser/ash/net/dns_over_https/templates_uri_resolver.h"
#include "chrome/browser/net/secure_dns_config.h"
#include "chrome/browser/net/stub_resolver_config_reader.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/network/network_ui_data.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/test_helper.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace ash {
namespace {
using testing::_;
using testing::Contains;
using testing::Key;
using testing::Return;
using testing::SizeIs;
constexpr const char kGoogleDns[] = "https://dns.google/dns-query{?dns}";
constexpr const char kCloudflareDns[] =
"https://chrome.cloudflare-dns.com/dns-query";
constexpr const char kMultipleTemplates[] =
"https://dns.google/dns-query{?dns} "
"https://chrome.cloudflare-dns.com/dns-query ";
class MockDoHTemplatesUriResolver
: public dns_over_https::TemplatesUriResolver {
public:
MockDoHTemplatesUriResolver() = default;
MOCK_METHOD(void,
Update,
(const PrefService&, const user_manager::User&),
(override));
MOCK_METHOD(bool, GetDohWithIdentifiersActive, (), (override));
MOCK_METHOD(std::string, GetEffectiveTemplates, (), (override));
MOCK_METHOD(std::string, GetDisplayTemplates, (), (override));
};
void OnGetDictProperties(const std::string& prop_name,
bool* success_out,
std::map<std::string, std::string>* props_out,
base::OnceClosure callback,
std::optional<base::Value::Dict> result) {
*success_out = result.has_value();
if (result) {
base::Value::Dict* value = result->FindDict(prop_name);
if (value != nullptr) {
for (const auto kv : *value) {
props_out->emplace(kv.first, kv.second.GetString());
}
}
}
std::move(callback).Run();
}
void OnGetStringsProperties(const std::string& prop_name,
bool* success_out,
std::vector<std::string>* props_out,
base::OnceClosure callback,
std::optional<base::Value::Dict> result) {
*success_out = result.has_value();
if (result) {
base::Value::List* value = result->FindList(prop_name);
if (value != nullptr) {
for (const auto& e : *value) {
props_out->push_back(e.GetString());
}
}
}
std::move(callback).Run();
}
std::map<std::string, std::string> GetDOHProviders() {
bool success = false;
std::map<std::string, std::string> props;
ShillManagerClient* shill_manager = ShillManagerClient::Get();
base::RunLoop run_loop;
shill_manager->GetProperties(
base::BindOnce(&OnGetDictProperties, shill::kDNSProxyDOHProvidersProperty,
base::Unretained(&success), base::Unretained(&props),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(success);
return props;
}
std::vector<std::string> GetDOHIncludedDomains() {
bool success = false;
std::vector<std::string> domains;
ShillManagerClient* shill_manager = ShillManagerClient::Get();
base::RunLoop run_loop;
shill_manager->GetProperties(base::BindOnce(
&OnGetStringsProperties, shill::kDOHIncludedDomainsProperty,
base::Unretained(&success), base::Unretained(&domains),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(success);
return domains;
}
std::vector<std::string> GetDOHExcludedDomains() {
bool success = false;
std::vector<std::string> domains;
ShillManagerClient* shill_manager = ShillManagerClient::Get();
base::RunLoop run_loop;
shill_manager->GetProperties(base::BindOnce(
&OnGetStringsProperties, shill::kDOHExcludedDomainsProperty,
base::Unretained(&success), base::Unretained(&domains),
run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(success);
return domains;
}
class SecureDnsManagerObserver : public SecureDnsManager::Observer {
public:
explicit SecureDnsManagerObserver(
raw_ptr<SecureDnsManager> secure_dns_manager)
: secure_dns_manager_(secure_dns_manager) {
secure_dns_manager_->AddObserver(this);
}
SecureDnsManagerObserver(const SecureDnsManagerObserver&) = delete;
SecureDnsManagerObserver& operator=(const SecureDnsManagerObserver&) = delete;
~SecureDnsManagerObserver() override {
if (secure_dns_manager_) {
secure_dns_manager_->RemoveObserver(this);
}
}
std::string doh_mode() const { return doh_mode_; }
std::string doh_template_uri() const { return doh_template_uri_; }
private:
void OnTemplateUrisChanged(const std::string& template_uris) override {
doh_template_uri_ = template_uris;
}
void OnModeChanged(const std::string& mode) override { doh_mode_ = mode; }
void OnSecureDnsManagerShutdown() override {
secure_dns_manager_->RemoveObserver(this);
secure_dns_manager_ = nullptr;
}
raw_ptr<SecureDnsManager> secure_dns_manager_;
std::string doh_mode_;
std::string doh_template_uri_;
};
class SecureDnsManagerTest : public testing::Test {
public:
SecureDnsManagerTest() = default;
SecureDnsManagerTest(const SecureDnsManagerTest&) = delete;
SecureDnsManagerTest& operator=(const SecureDnsManagerTest&) = delete;
void SetUp() override {
SecureDnsManager::RegisterProfilePrefs(profile_prefs_.registry());
SecureDnsManager::RegisterLocalStatePrefs(local_state_.registry());
local_state_.registry()->RegisterStringPref(::prefs::kDnsOverHttpsMode,
SecureDnsConfig::kModeOff);
local_state_.registry()->RegisterStringPref(::prefs::kDnsOverHttpsTemplates,
"");
local_state_.registry()->RegisterStringPref(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS, "");
local_state_.registry()->RegisterListPref(
prefs::kDnsOverHttpsExcludedDomains, base::Value::List());
local_state_.registry()->RegisterListPref(
prefs::kDnsOverHttpsIncludedDomains, base::Value::List());
local_state_.registry()->RegisterBooleanPref(
::prefs::kBuiltInDnsClientEnabled, true);
local_state_.registry()->RegisterBooleanPref(
::prefs::kAdditionalDnsQueryTypesEnabled, true);
// Add a user for test.
user_manager::UserManagerImpl::RegisterPrefs(local_state_.registry());
fake_user_manager_.Reset(
std::make_unique<user_manager::FakeUserManager>(&local_state_));
const AccountId account_id = AccountId::FromUserEmailGaiaId(
"test-user@testdomain.com", GaiaId("1234567890"));
user_ = fake_user_manager_->AddGaiaUser(account_id,
user_manager::UserType::kRegular);
ASSERT_TRUE(user_);
network_handler_test_helper_.RegisterPrefs(profile_prefs_.registry(),
local_state_.registry());
network_handler_test_helper_.InitializePrefs(&profile_prefs_,
&local_state_);
network_handler_test_helper_.AddDefaultProfiles();
// Simulate login.
fake_user_manager_->UserLoggedIn(
account_id, user_manager::TestHelper::GetFakeUsernameHash(account_id));
fake_user_manager_->OnUserProfileCreated(account_id, &profile_prefs_);
// SystemNetworkContextManager cannot be instantiated here,
// which normally owns the StubResolverConfigReader instance, so
// inject a StubResolverConfigReader instance here.
stub_resolver_config_reader_ =
std::make_unique<StubResolverConfigReader>(&local_state_);
SystemNetworkContextManager::set_stub_resolver_config_reader_for_testing(
stub_resolver_config_reader_.get());
secure_dns_manager_ = std::make_unique<SecureDnsManager>(
local_state(), *user_, /*is_profile_managed=*/true);
secure_dns_manager_observer_ =
std::make_unique<SecureDnsManagerObserver>(secure_dns_manager_.get());
}
void TearDown() override {
NetworkHandler::Get()->ShutdownPrefServices();
secure_dns_manager_observer_.reset();
secure_dns_manager_.reset();
SystemNetworkContextManager::set_stub_resolver_config_reader_for_testing(
nullptr);
stub_resolver_config_reader_.reset();
fake_user_manager_->OnUserProfileWillBeDestroyed(
AccountId::FromUserEmailGaiaId("test-user@testdomain.com",
GaiaId("1234567890")));
user_ = nullptr;
fake_user_manager_.Reset();
}
void ChangeNetworkOncSource(const std::string& path,
::onc::ONCSource onc_source) {
std::unique_ptr<ash::NetworkUIData> ui_data =
ash::NetworkUIData::CreateFromONC(onc_source);
network_handler_test_helper_.SetServiceProperty(
path, shill::kUIDataProperty, base::Value(ui_data->GetAsJson()));
}
void ResetSecureDnsManager() {
secure_dns_manager_observer_.reset();
secure_dns_manager_.reset();
}
TestingPrefServiceSimple* local_state() { return &local_state_; }
user_manager::User& user() { return *user_; }
PrefService* profile_prefs() { return &profile_prefs_; }
SecureDnsManager* secure_dns_manager() { return secure_dns_manager_.get(); }
SecureDnsManagerObserver* secure_dns_manager_observer() {
return secure_dns_manager_observer_.get();
}
private:
content::BrowserTaskEnvironment task_environment_;
NetworkHandlerTestHelper network_handler_test_helper_;
std::unique_ptr<StubResolverConfigReader> stub_resolver_config_reader_;
TestingPrefServiceSimple local_state_;
user_manager::TypedScopedUserManager<user_manager::FakeUserManager>
fake_user_manager_;
raw_ptr<user_manager::User> user_;
TestingPrefServiceSimple profile_prefs_;
std::unique_ptr<SecureDnsManager> secure_dns_manager_;
std::unique_ptr<SecureDnsManagerObserver> secure_dns_manager_observer_;
};
TEST_F(SecureDnsManagerTest, SetModeOff) {
local_state()->Set(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeOff));
auto providers = GetDOHProviders();
EXPECT_TRUE(providers.empty());
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
"");
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
base::Value(SecureDnsConfig::kModeOff));
}
TEST_F(SecureDnsManagerTest, SetModeOffIgnoresTemplates) {
local_state()->Set(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeOff));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
auto providers = GetDOHProviders();
EXPECT_TRUE(providers.empty());
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
"");
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeOff);
}
TEST_F(SecureDnsManagerTest, SetModeSecure) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
auto providers = GetDOHProviders();
const auto it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_EQ(it->first, kGoogleDns);
EXPECT_TRUE(it->second.empty());
EXPECT_EQ(providers.size(), 1u);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeSecure);
}
TEST_F(SecureDnsManagerTest, SetModeSecureMultipleTemplates) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates,
base::Value(kMultipleTemplates));
auto providers = GetDOHProviders();
EXPECT_TRUE(providers.find(kGoogleDns) != providers.end());
EXPECT_TRUE(providers.find(kCloudflareDns) != providers.end());
EXPECT_EQ(providers.size(), 2u);
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
kMultipleTemplates);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
kMultipleTemplates);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeSecure);
}
TEST_F(SecureDnsManagerTest, SetModeSecureWithFallback) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
auto providers = GetDOHProviders();
const auto it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_EQ(it->first, kGoogleDns);
EXPECT_EQ(it->second, "*");
EXPECT_EQ(providers.size(), 1u);
}
TEST_F(SecureDnsManagerTest, SetModeSecureWithFallbackMultipleTemplates) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplates,
base::Value(kMultipleTemplates));
auto providers = GetDOHProviders();
EXPECT_TRUE(providers.find(kGoogleDns) != providers.end());
EXPECT_TRUE(providers.find(kCloudflareDns) != providers.end());
EXPECT_EQ(providers.size(), 2u);
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
kMultipleTemplates);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
kMultipleTemplates);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeAutomatic);
}
TEST_F(SecureDnsManagerTest, SetModeAutomaticWithTemplates) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplates,
base::Value(kMultipleTemplates));
auto providers = GetDOHProviders();
auto it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_FALSE(it->second.empty());
it = providers.find(kCloudflareDns);
EXPECT_TRUE(it != providers.end());
EXPECT_FALSE(it->second.empty());
EXPECT_EQ(providers.size(), 2u);
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
kMultipleTemplates);
}
// Tests that the `DoHTemplatesUriResolver` resolver is called when secure DNS
// prefs change and that the result, provided by `GetEffectiveTemplates` is
// read.
TEST_F(SecureDnsManagerTest, DoHTemplatesUriResolverCalled) {
constexpr char effectiveTemplate[] = "effectiveTemplate";
// The test will update the four prefs that `SecureDnsManager` is observing.
constexpr int prefUpdatesCallCount = 4;
std::unique_ptr<MockDoHTemplatesUriResolver> template_uri_resolver =
std::make_unique<MockDoHTemplatesUriResolver>();
EXPECT_CALL(*template_uri_resolver, Update(_, _)).Times(prefUpdatesCallCount);
EXPECT_CALL(*template_uri_resolver, GetEffectiveTemplates())
.Times(prefUpdatesCallCount)
.WillRepeatedly(Return(effectiveTemplate));
secure_dns_manager()->SetDoHTemplatesUriResolverForTesting(
std::move(template_uri_resolver));
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplates,
base::Value(kMultipleTemplates));
local_state()->Set(::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
base::Value(kMultipleTemplates));
local_state()->Set(::prefs::kDnsOverHttpsSalt, base::Value("testsalt"));
auto providers = GetDOHProviders();
EXPECT_THAT(providers, SizeIs(1));
EXPECT_THAT(providers, Contains(Key(effectiveTemplate)));
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
effectiveTemplate);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
effectiveTemplate);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeAutomatic);
}
TEST_F(SecureDnsManagerTest, NetworkMetadataStoreHasDohWithIdentifiersActive) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
base::Value("https://dns.google/dns-query{?dns}"));
local_state()->Set(::prefs::kDnsOverHttpsSalt, base::Value("testsalt"));
auto providers = GetDOHProviders();
EXPECT_TRUE(NetworkHandler::Get()
->network_metadata_store()
->secure_dns_templates_with_identifiers_active());
local_state()->ClearPref(::prefs::kDnsOverHttpsTemplatesWithIdentifiers);
providers = GetDOHProviders();
EXPECT_FALSE(NetworkHandler::Get()
->network_metadata_store()
->secure_dns_templates_with_identifiers_active());
}
TEST_F(SecureDnsManagerTest, kDnsOverHttpsEffectiveTemplatesChromeOS) {
constexpr char kUriTemplateWithIdentifiers[] =
"https://dns.google.alternativeuri/"
"${USER_EMAIL}/{?dns}";
constexpr char kEffectiveUriTemplateWithIdentifiers[] =
"https://dns.google.alternativeuri/"
"B07D2C5D119EB1881671C3B8D84CBE4FE3595C0C9ECBBF7670B18DDFDA072F66/{?dns}";
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
base::Value(kUriTemplateWithIdentifiers));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
auto providers = GetDOHProviders();
// Verify that the value of kDnsOverHttpsEffectiveTemplatesChromeOS pref is
// ::prefs::kDnsOverHttpsTemplatesWithIdentifiers with the hex encoded hashed
// value of the user identifier.
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
kEffectiveUriTemplateWithIdentifiers);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(),
kEffectiveUriTemplateWithIdentifiers);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeAutomatic);
local_state()->ClearPref(::prefs::kDnsOverHttpsTemplatesWithIdentifiers);
providers = GetDOHProviders();
// Verify that the value of kDnsOverHttpsEffectiveTemplatesChromeOS pref is
// prefs::kDnsOverHttpsTemplates since the URI template with identifiers pref
// was cleared.
EXPECT_EQ(local_state()->GetString(
::prefs::kDnsOverHttpsEffectiveTemplatesChromeOS),
kGoogleDns);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeAutomatic);
}
TEST_F(SecureDnsManagerTest, DefaultNetworkObservedForIpAddressPlaceholder) {
constexpr char kUriTemplateWithEmail[] =
"https://dns.google.alternativeuri/"
"${USER_EMAIL}/{?dns}";
constexpr char kUriTemplateWithIp[] =
"https://dns.google.alternativeuri/"
"${DEVICE_IP_ADDRESSES}/{?dns}";
int expected_uri_template_update_count = 0;
int actual_uri_template_update_count = 0;
std::unique_ptr<MockDoHTemplatesUriResolver> template_uri_resolver =
std::make_unique<MockDoHTemplatesUriResolver>();
ON_CALL(*template_uri_resolver, Update(_, _))
.WillByDefault(testing::Invoke([&actual_uri_template_update_count]() {
actual_uri_template_update_count++;
}));
EXPECT_CALL(*template_uri_resolver, GetDohWithIdentifiersActive())
.WillRepeatedly(testing::Return(true));
secure_dns_manager()->SetDoHTemplatesUriResolverForTesting(
std::move(template_uri_resolver));
EXPECT_EQ(actual_uri_template_update_count,
expected_uri_template_update_count);
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
base::Value(kUriTemplateWithEmail));
// Each pref update above will trigger an update request for the URI
// templates.
expected_uri_template_update_count = 2;
EXPECT_EQ(actual_uri_template_update_count,
expected_uri_template_update_count);
const ash::NetworkState* network =
ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
ChangeNetworkOncSource(network->path(),
::onc::ONCSource::ONC_SOURCE_USER_POLICY);
// Default network changes should not trigger a re-evaluation of the templates
// URI if the DoH policy is not configured to use the device IP addresses.
EXPECT_EQ(actual_uri_template_update_count,
expected_uri_template_update_count);
local_state()->Set(::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
base::Value(kUriTemplateWithIp));
EXPECT_EQ(actual_uri_template_update_count,
++expected_uri_template_update_count);
ChangeNetworkOncSource(network->path(),
::onc::ONCSource::ONC_SOURCE_USER_POLICY);
EXPECT_EQ(actual_uri_template_update_count,
++expected_uri_template_update_count);
}
TEST_F(SecureDnsManagerTest, DefaultTemplateUrisForwardedToShill) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
auto providers = GetDOHProviders();
// The content of the provider list depends on the current country.
EXPECT_FALSE(providers.empty());
}
class MockPropertyChangeObserver : public ash::ShillPropertyChangedObserver {
public:
MockPropertyChangeObserver() = default;
~MockPropertyChangeObserver() override = default;
MOCK_METHOD(void,
OnPropertyChanged,
(const std::string& name, const base::Value& value),
(override));
};
TEST_F(SecureDnsManagerTest, NoDuplicateShillPropertyUpdateRequests) {
constexpr char kTemplateUri1[] = "https://dns.google1.com";
constexpr char kTemplateUri2[] = "https://dns.google2.com";
constexpr char kEffectiveTemplateUri[] = "https://dns.google2.com";
// The call to update the shill properties should be invoked just once. For
// kDOHIncludedDomainsProperty, the mock TemplateUriResolver always returns
// the same DoH providers.
testing::StrictMock<MockPropertyChangeObserver> observer;
EXPECT_CALL(observer,
OnPropertyChanged(shill::kDOHIncludedDomainsProperty, testing::_))
.Times(1);
EXPECT_CALL(observer,
OnPropertyChanged(shill::kDOHExcludedDomainsProperty, testing::_))
.Times(1);
// The two calls are generated:
// 1. at startup, with an empty value
// 2. after the template URI resolver is configured
EXPECT_CALL(observer, OnPropertyChanged(shill::kDNSProxyDOHProvidersProperty,
testing::_))
.Times(2);
ash::ShillManagerClient* shill_manager_client =
ash::ShillManagerClient::Get();
shill_manager_client->AddPropertyChangedObserver(&observer);
int actual_uri_template_update_count = 0;
std::unique_ptr<MockDoHTemplatesUriResolver> template_uri_resolver =
std::make_unique<MockDoHTemplatesUriResolver>();
ON_CALL(*template_uri_resolver, Update(_, _))
.WillByDefault(testing::Invoke([&actual_uri_template_update_count]() {
actual_uri_template_update_count++;
}));
EXPECT_CALL(*template_uri_resolver, GetDohWithIdentifiersActive())
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*template_uri_resolver, GetEffectiveTemplates())
.WillRepeatedly(testing::Return(kEffectiveTemplateUri));
secure_dns_manager()->SetDoHTemplatesUriResolverForTesting(
std::move(template_uri_resolver));
EXPECT_EQ(actual_uri_template_update_count, 0);
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
base::Value(kTemplateUri1));
local_state()->Set(::prefs::kDnsOverHttpsTemplatesWithIdentifiers,
base::Value(kTemplateUri2));
// Verify that every pref update above will trigger an update request for the
// DoH providers.
EXPECT_EQ(actual_uri_template_update_count, 3);
base::RunLoop().RunUntilIdle();
}
TEST_F(SecureDnsManagerTest, SetDOHIncludedDomains) {
std::vector<std::string> domains = {"test.com", "*.test.com"};
base::Value pref_value(base::Value::Type::LIST);
for (const auto& domain : domains) {
pref_value.GetList().Append(domain);
}
local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
EXPECT_EQ(domains, GetDOHIncludedDomains());
}
TEST_F(SecureDnsManagerTest, SetDOHExcludedDomains) {
std::vector<std::string> domains = {"test.com", "*.test.com"};
base::Value pref_value(base::Value::Type::LIST);
for (const auto& domain : domains) {
pref_value.GetList().Append(domain);
}
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
EXPECT_EQ(domains, GetDOHExcludedDomains());
}
// This test verifies the user-set local_state to user-set profile_prefs
// migration logic for DoH prefs.
TEST_F(SecureDnsManagerTest, LocalStateToProfilePrefMigration) {
local_state()->Set(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsMode), "");
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsTemplates), "");
{
auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
local_state(), user(), /*is_profile_managed=*/false);
// Verify that the user-set local state prefs are copied to profile prefs
// for unmanaged users.
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsMode),
SecureDnsConfig::kModeSecure);
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsTemplates),
kGoogleDns);
}
profile_prefs()->ClearPref(::prefs::kDnsOverHttpsMode);
profile_prefs()->ClearPref(::prefs::kDnsOverHttpsTemplates);
{
auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
local_state(), user(), /*is_profile_managed=*/true);
// Verify that the user-set local state prefs are not copied to profile
// prefs for managed users. SecureDnsConfig::kModeAutomatic is the default
// value for secure DoH mode.
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsMode),
SecureDnsConfig::kModeAutomatic);
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsTemplates), "");
}
profile_prefs()->Set(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
profile_prefs()->Set(::prefs::kDnsOverHttpsTemplates,
base::Value(kCloudflareDns));
{
auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
local_state(), user(), /*is_profile_managed=*/false);
// When the profile prefs already have DoH prefs configured, verify that the
// pref migration will not override them.
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsMode),
SecureDnsConfig::kModeSecure);
EXPECT_EQ(profile_prefs()->GetString(::prefs::kDnsOverHttpsTemplates),
kCloudflareDns);
}
}
// This test verifies that the SecureDnsManager updates observers with the
// correct DoH configuration when the user profile is not managed.
TEST_F(SecureDnsManagerTest, ObserverForUnmanagedUsers) {
local_state()->Set(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeAutomatic));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
auto consumer_secure_dns_manager = std::make_unique<SecureDnsManager>(
local_state(), user(), /*is_profile_managed=*/false);
auto consumer_observer = std::make_unique<SecureDnsManagerObserver>(
consumer_secure_dns_manager.get());
EXPECT_EQ(consumer_observer->doh_template_uri(), kGoogleDns);
EXPECT_EQ(consumer_observer->doh_mode(), SecureDnsConfig::kModeAutomatic);
profile_prefs()->Set(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
profile_prefs()->Set(::prefs::kDnsOverHttpsTemplates,
base::Value(kCloudflareDns));
EXPECT_EQ(consumer_observer->doh_template_uri(), kCloudflareDns);
EXPECT_EQ(consumer_observer->doh_mode(), SecureDnsConfig::kModeSecure);
}
TEST_F(SecureDnsManagerTest, DohIncludedDomains_ChromeDohConfig) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
// Set DoHIncludedDomains, expect Chrome DoH to be disabled.
base::Value pref_value(base::Value::Type::LIST);
pref_value.GetList().Append("test.com");
local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeOff);
// Unset DoHIncludedDomains, expect Chrome DoH to be re-enabled.
pref_value.GetList().clear();
local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeSecure);
}
TEST_F(SecureDnsManagerTest, DohExcludedDomains_ChromeDohConfig) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
// Set DoHExcludedDomains, expect Chrome DoH to be disabled.
base::Value pref_value(base::Value::Type::LIST);
pref_value.GetList().Append("test.com");
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeOff);
// Unset DoHExcludedDomains, expect Chrome DoH to be re-enabled.
pref_value.GetList().clear();
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeSecure);
}
TEST_F(SecureDnsManagerTest, DohDomainConfig_ChromeDohConfig) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
// Set DoHIncludedDomains, expect Chrome DoH to be disabled.
base::Value pref_value(base::Value::Type::LIST);
pref_value.GetList().Append("include.com");
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeOff);
// Set DoHExcludedDomains, expect Chrome DoH to still be disabled.
pref_value.GetList().clear();
pref_value.GetList().Append("exclude.com");
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeOff);
// Unset DoHIncludedDomains, expect Chrome DoH to still be disabled.
pref_value.GetList().clear();
local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), "");
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeOff);
// Unset DoHExcludedDomains, expect Chrome DoH to be re-enabled.
pref_value.GetList().clear();
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
EXPECT_EQ(secure_dns_manager_observer()->doh_template_uri(), kGoogleDns);
EXPECT_EQ(secure_dns_manager_observer()->doh_mode(),
SecureDnsConfig::kModeSecure);
}
TEST_F(SecureDnsManagerTest, DohIncludedDomains_ShillDohConfig) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
// Set DoHIncludedDomains, expect no change to shill DoH config.
base::Value pref_value(base::Value::Type::LIST);
pref_value.GetList().Append("test.com");
local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
auto providers = GetDOHProviders();
auto it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_EQ(it->first, kGoogleDns);
EXPECT_TRUE(it->second.empty());
EXPECT_EQ(providers.size(), 1u);
// Unset DoHIncludedDomains, expect no change to shill DoH config.
pref_value.GetList().clear();
local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
providers = GetDOHProviders();
it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_EQ(it->first, kGoogleDns);
EXPECT_TRUE(it->second.empty());
EXPECT_EQ(providers.size(), 1u);
}
TEST_F(SecureDnsManagerTest, DohExcludedDomains_ShillDohConfig) {
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
// Set DoHExcludedDomains, expect no change to shill DoH config.
base::Value pref_value(base::Value::Type::LIST);
pref_value.GetList().Append("test.com");
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
auto providers = GetDOHProviders();
auto it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_EQ(it->first, kGoogleDns);
EXPECT_TRUE(it->second.empty());
EXPECT_EQ(providers.size(), 1u);
// Unset DoHExcludedDomains, expect no change to shill DoH config.
pref_value.GetList().clear();
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
providers = GetDOHProviders();
it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_EQ(it->first, kGoogleDns);
EXPECT_TRUE(it->second.empty());
EXPECT_EQ(providers.size(), 1u);
}
TEST_F(SecureDnsManagerTest, ResetShillState) {
// Set DnsOverHttpsMode and DnsOverHttpsTemplates.
local_state()->SetManagedPref(::prefs::kDnsOverHttpsMode,
base::Value(SecureDnsConfig::kModeSecure));
local_state()->Set(::prefs::kDnsOverHttpsTemplates, base::Value(kGoogleDns));
auto providers = GetDOHProviders();
auto it = providers.find(kGoogleDns);
EXPECT_TRUE(it != providers.end());
EXPECT_EQ(it->first, kGoogleDns);
EXPECT_TRUE(it->second.empty());
EXPECT_EQ(providers.size(), 1u);
// Set DnsOverHttpsIncludedDomains and DnsOverHttpsExcludedDomains.
std::vector<std::string> domains = {"test.com", "*.test.com"};
base::Value pref_value(base::Value::Type::LIST);
for (const auto& domain : domains) {
pref_value.GetList().Append(domain);
}
local_state()->Set(prefs::kDnsOverHttpsIncludedDomains, pref_value);
local_state()->Set(prefs::kDnsOverHttpsExcludedDomains, pref_value);
EXPECT_EQ(domains, GetDOHIncludedDomains());
EXPECT_EQ(domains, GetDOHExcludedDomains());
// Expect Shill's state to be cleared when the class is destroyed.
ResetSecureDnsManager();
EXPECT_TRUE(GetDOHProviders().empty());
EXPECT_TRUE(GetDOHIncludedDomains().empty());
EXPECT_TRUE(GetDOHExcludedDomains().empty());
}
} // namespace
} // namespace ash