// Copyright 2023 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/alwayson_vpn_pre_connect_url_allowlist_service.h"
#include <memory>
#include "ash/components/arc/arc_prefs.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/net/alwayson_vpn_pre_connect_url_allowlist_service_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "components/account_id/account_id.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace {
// The network paths for the default services created by the
// FakeShillManagerClient.
constexpr char kDefaultEthernetPath[] = "/service/eth1";
constexpr char kDefaultVpnPath[] = "/service/vpn1";
constexpr char kFakePrimaryUsername[] = "[email protected]";
constexpr char kTestUrl[] = "test.url.com";
} // namespace
namespace ash {
class AlwaysOnVpnPreConnectUrlAllowlistServiceTest
: public InProcessBrowserTest {
public:
AlwaysOnVpnPreConnectUrlAllowlistServiceTest() {}
AlwaysOnVpnPreConnectUrlAllowlistServiceTest(
const AlwaysOnVpnPreConnectUrlAllowlistServiceTest&) = delete;
AlwaysOnVpnPreConnectUrlAllowlistServiceTest& operator=(
const AlwaysOnVpnPreConnectUrlAllowlistServiceTest&) = delete;
~AlwaysOnVpnPreConnectUrlAllowlistServiceTest() override = default;
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
ash::AlwaysOnVpnPreConnectUrlAllowlistServiceFactory::GetInstance()
->SetServiceIsNULLWhileTestingForTesting(
/*service_is_null_while_testing=*/false);
}
void TearDownOnMainThread() override {
InProcessBrowserTest::TearDownOnMainThread();
ash::AlwaysOnVpnPreConnectUrlAllowlistServiceFactory::GetInstance()
->SetServiceIsNULLWhileTestingForTesting(
/*service_is_null_while_testing=*/true);
}
};
IN_PROC_BROWSER_TEST_F(AlwaysOnVpnPreConnectUrlAllowlistServiceTest,
NoServiceForUnmanagedProfiles) {
// The service should be null because the profile is not managed.
EXPECT_FALSE(
ash::AlwaysOnVpnPreConnectUrlAllowlistServiceFactory::GetForProfile(
browser()->profile()));
}
IN_PROC_BROWSER_TEST_F(AlwaysOnVpnPreConnectUrlAllowlistServiceTest,
NoServiceForSecondaryProfiles) {
// Create a secondary managed profile.
TestingProfile::Builder profile_builder;
profile_builder.OverridePolicyConnectorIsManagedForTesting(
/*is_managed*/ true);
std::unique_ptr<TestingProfile> managed_profile = profile_builder.Build();
EXPECT_FALSE(
ash::AlwaysOnVpnPreConnectUrlAllowlistServiceFactory::GetForProfile(
browser()->profile()));
}
class AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest
: public AlwaysOnVpnPreConnectUrlAllowlistServiceTest {
public:
AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest() {}
AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest(
const AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest&) =
delete;
AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest& operator=(
const AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest&) =
delete;
~AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest() override =
default;
void SetUpOnMainThread() override {
AlwaysOnVpnPreConnectUrlAllowlistServiceTest::SetUpOnMainThread();
network_handler_test_helper_ =
std::make_unique<::ash::NetworkHandlerTestHelper>();
ShillServiceClient::Get()->GetTestInterface()->SetServiceProperty(
"/service/wifi1", shill::kStateProperty,
base::Value(shill::kStateIdle));
// Create a primary managed profile.
fake_user_manager_.Reset(std::make_unique<ash::FakeChromeUserManager>());
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
TestingProfile::Builder profile_builder;
profile_builder.SetPath(temp_dir_.GetPath().AppendASCII(
BrowserContextHelper::GetUserBrowserContextDirName(
user_manager::FakeUserManager::GetFakeUsernameHash(
AccountId::FromUserEmail(kFakePrimaryUsername)))));
profile_builder.SetProfileName(kFakePrimaryUsername);
profile_builder.OverridePolicyConnectorIsManagedForTesting(
/*is_managed*/ true);
managed_profile_ = profile_builder.Build();
primary_account_id_ =
AccountId::FromUserEmail(managed_profile_->GetProfileUserName());
const user_manager::User* user =
fake_user_manager_->AddPublicAccountUser(primary_account_id_);
fake_user_manager_->UserLoggedIn(primary_account_id_, user->username_hash(),
false /* browser_restart */,
false /* is_child */);
fake_user_manager_->SetIsCurrentUserNew(/*is_new=*/true);
// The AlwaysOnVpnPreConnectUrlAllowlistService keyed service for this
// profile needs to be disassociated with the null object so that a new
// instance of the service is created.
ash::AlwaysOnVpnPreConnectUrlAllowlistServiceFactory::GetInstance()
->RecreateServiceInstanceForTesting(managed_profile_.get());
// The service should be created for a managed profile.
ASSERT_TRUE(
ash::AlwaysOnVpnPreConnectUrlAllowlistServiceFactory::GetForProfile(
managed_profile()));
}
void TearDownOnMainThread() override {
fake_user_manager_->RemoveUserFromList(primary_account_id_);
managed_profile_.reset();
fake_user_manager_.Reset();
AlwaysOnVpnPreConnectUrlAllowlistServiceTest::TearDownOnMainThread();
}
protected:
void SetVpnConnectionState(bool vpn_enabled) {
ShillServiceClient::TestInterface* service =
ShillServiceClient::Get()->GetTestInterface();
service->SetServiceProperty(
kDefaultEthernetPath, shill::kStateProperty,
base::Value(vpn_enabled ? shill::kStateIdle : shill::kStateOnline));
service->SetServiceProperty(
kDefaultVpnPath, shill::kStateProperty,
base::Value(vpn_enabled ? shill::kStateOnline : shill::kStateIdle));
base::RunLoop().RunUntilIdle();
}
// The kAlwaysOnVpnPreConnectUrlAllowlist should be enfoced when the following
// conditions meet:
// - The kAlwaysOnVpnPreConnectUrlAllowlist list is not empty;
// - AlwaysOn VPN is active on the device (.i.e. the
// arc::prefs::kAlwaysOnVpnLockdown pref is true);
// - The VPN is not yet connected.
void CreatePreConnectListEnv() {
SetVpnConnectionState(/*vpn_enabled=*/false);
const ash::NetworkState* network =
ash::NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
EXPECT_NE(network->GetNetworkTechnologyType(),
ash::NetworkState::NetworkTechnologyType::kVPN);
base::Value::List list;
list.Append(kTestUrl);
managed_profile()->GetPrefs()->SetList(
policy::policy_prefs::kAlwaysOnVpnPreConnectUrlAllowlist,
std::move(list));
managed_profile()->GetPrefs()->SetBoolean(arc::prefs::kAlwaysOnVpnLockdown,
true);
}
bool IsPreConnectListEnforced() {
return ash::AlwaysOnVpnPreConnectUrlAllowlistServiceFactory::GetForProfile(
managed_profile())
->enforce_alwayson_pre_connect_url_allowlist();
}
Profile* managed_profile() { return managed_profile_.get(); }
private:
std::unique_ptr<::ash::NetworkHandlerTestHelper> network_handler_test_helper_;
std::unique_ptr<AlwaysOnVpnPreConnectUrlAllowlistService>
alwayson_vpn_pre_connect_url_allowlist_service_;
std::unique_ptr<TestingProfile> managed_profile_;
base::ScopedTempDir temp_dir_;
user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
fake_user_manager_;
AccountId primary_account_id_;
};
// Ensure that the local state pref kEnforceAlwaysOnVpnPreConnectUrlAllowlist is
// true when the kAlwaysOnVpnPreConnectUrlAllowlist is set but the AlwaysOn VPN
// is not yet connected.
IN_PROC_BROWSER_TEST_F(
AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest,
PreConnectListEnabled) {
EXPECT_FALSE(IsPreConnectListEnforced());
CreatePreConnectListEnv();
EXPECT_TRUE(IsPreConnectListEnforced());
}
// Ensure that the local state pref kEnforceAlwaysOnVpnPreConnectUrlAllowlist is
// false when the VPN is connected.
IN_PROC_BROWSER_TEST_F(
AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest,
VpnConnected) {
CreatePreConnectListEnv();
EXPECT_TRUE(IsPreConnectListEnforced());
// Simulate the VPN state = connected.
SetVpnConnectionState(/*vpn_enabled*/ true);
EXPECT_FALSE(IsPreConnectListEnforced());
// Simulate the VPN state = disconnected and check that the pre-connect list
// is again enfoced.
SetVpnConnectionState(/*vpn_enabled*/ false);
EXPECT_TRUE(IsPreConnectListEnforced());
}
// Ensure that the local state pref kEnforceAlwaysOnVpnPreConnectUrlAllowlist is
// false when the kAlwaysOnVpnPreConnectUrlAllowlist pref is not set.
IN_PROC_BROWSER_TEST_F(
AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest,
AlwaysOnVpnPreConnectUrlAllowlistEmpty) {
CreatePreConnectListEnv();
EXPECT_TRUE(IsPreConnectListEnforced());
// Create and set an empty list.
base::Value::List list;
managed_profile()->GetPrefs()->SetList(
policy::policy_prefs::kAlwaysOnVpnPreConnectUrlAllowlist, list.Clone());
EXPECT_FALSE(IsPreConnectListEnforced());
// Set a value for the kAlwaysOnVpnPreConnectUrlAllowlist pref again and
// verify that the pre-connect list is again enforced.
list.Append(kTestUrl);
managed_profile()->GetPrefs()->SetList(
policy::policy_prefs::kAlwaysOnVpnPreConnectUrlAllowlist,
std::move(list));
EXPECT_TRUE(IsPreConnectListEnforced());
}
// Ensure that the local state pref kEnforceAlwaysOnVpnPreConnectUrlAllowlist is
// false when the VPN is not in lockdown mode.
IN_PROC_BROWSER_TEST_F(
AlwaysOnVpnPreConnectUrlAllowlistServiceManagedProfileTest,
AlwaysOnVpnFalse) {
CreatePreConnectListEnv();
EXPECT_TRUE(IsPreConnectListEnforced());
// Remove lockdown mode.
managed_profile()->GetPrefs()->SetBoolean(arc::prefs::kAlwaysOnVpnLockdown,
false);
EXPECT_FALSE(IsPreConnectListEnforced());
// Set lockdown mode and verify that the pre-connect list is again
// enforced.
managed_profile()->GetPrefs()->SetBoolean(arc::prefs::kAlwaysOnVpnLockdown,
true);
EXPECT_TRUE(IsPreConnectListEnforced());
}
} // namespace ash