// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/system_tray_test_api.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/policy/networking/network_configuration_updater.h"
#include "chrome/browser/ui/ash/network/enrollment_dialog_view.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/ash/scoped_test_system_nss_key_slot_mixin.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ash/components/dbus/shill/shill_device_client.h"
#include "chromeos/ash/components/dbus/shill/shill_ipconfig_client.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_profile_client.h"
#include "chromeos/ash/components/dbus/shill/shill_property_changed_observer.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler.h"
#include "chromeos/ash/components/network/network_cert_loader.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_policy_observer.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "chromeos/ash/services/network_config/cros_network_config.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_observer.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "components/account_id/account_id.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/policy_constants.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "crypto/scoped_nss_types.h"
#include "dbus/object_path.h"
#include "net/cert/cert_database.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_utils.h"
#include "ui/views/window/dialog_delegate.h"
namespace policy {
namespace {
namespace network_mojom = ::chromeos::network_config::mojom;
using ::base::test::DictionaryHasValue;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::IsNull;
using ::testing::Not;
using ::testing::Pointee;
using ::testing::SizeIs;
constexpr char kUserProfilePath[] = "user_profile";
constexpr char kSharedProfilePath[] = "/profile/default";
constexpr char kDeviceWifiPath[] = "/device/wifi1";
constexpr char kServiceEth[] = "/service/0";
constexpr char kServiceWifi1[] = "/service/1";
constexpr char kServiceWifi2[] = "/service/2";
constexpr char kServiceWifi3[] = "/service/3";
constexpr char kServiceWifi4[] = "/service/4";
constexpr char kUIDataKeyUserSettings[] = "user_settings";
constexpr char kUserIdentity[] = "user_identity";
constexpr char kTestDomain[] = "test_domain";
constexpr char kTestDeviceId[] = "test_device_id";
// Records all values that shill service property had during the lifetime of
// ServicePropertyValueWatcher. Only supports string properties at the moment.
class ServicePropertyValueWatcher : public ash::ShillPropertyChangedObserver {
public:
ServicePropertyValueWatcher(
ash::ShillServiceClient::TestInterface* shill_service_client_test,
const std::string& service_path,
const std::string& property_name)
: shill_service_client_test_(shill_service_client_test),
service_path_(service_path),
property_name_(property_name) {
ash::ShillServiceClient::Get()->AddPropertyChangedObserver(
dbus::ObjectPath(service_path_), this);
// If the service already exists and has `property_name`, record the initial
// value.
const base::Value::Dict* initial_service_properties =
shill_service_client_test_->GetServiceProperties(service_path);
if (!initial_service_properties) {
return;
}
const std::string* property_value =
initial_service_properties->FindString(property_name);
if (!property_value) {
return;
}
values_.push_back(*property_value);
}
~ServicePropertyValueWatcher() override {
ash::ShillServiceClient::Get()->RemovePropertyChangedObserver(
dbus::ObjectPath(service_path_), this);
}
ServicePropertyValueWatcher(const ServicePropertyValueWatcher&) = delete;
ServicePropertyValueWatcher& operator=(const ServicePropertyValueWatcher&) =
delete;
void OnPropertyChanged(const std::string& name,
const base::Value& value) override {
if (name != property_name_) {
return;
}
if (!value.is_string()) {
return;
}
if (!values_.empty() && values_.back() == value.GetString()) {
return;
}
values_.push_back(value.GetString());
if (wait_for_value_state_ &&
wait_for_value_state_->predicate.Run(value.GetString())) {
wait_for_value_state_->run_loop.Quit();
}
}
// Returns all values that the property passed to the constructor had since
// this instance has been created.
const std::vector<std::string>& GetValues() { return values_; }
void WaitForNonEmptyValue() {
WaitForValue(base::BindRepeating(
[](const std::string& value) { return !value.empty(); }));
}
void WaitForValue(const std::string& expected_value) {
WaitForValue(
base::BindLambdaForTesting([expected_value](const std::string& value) {
return value == expected_value;
}));
}
private:
using ValuePredicate = base::RepeatingCallback<bool(const std::string&)>;
struct WaitForValueState {
explicit WaitForValueState(ValuePredicate predicate)
: predicate(predicate) {}
ValuePredicate predicate;
base::RunLoop run_loop;
};
void WaitForValue(ValuePredicate predicate) {
if (!values_.empty() && predicate.Run(values_.back())) {
return;
}
wait_for_value_state_.emplace(predicate);
wait_for_value_state_->run_loop.Run();
wait_for_value_state_.reset();
}
const raw_ptr<ash::ShillServiceClient::TestInterface>
shill_service_client_test_;
const std::string service_path_;
const std::string property_name_;
std::vector<std::string> values_;
std::optional<WaitForValueState> wait_for_value_state_;
};
// Shorthand for ServicePropertyValueWatcher that allows waiting for a specific
// shill::kStateProperty value.
class ServiceStateWaiter {
public:
ServiceStateWaiter(
ash::ShillServiceClient::TestInterface* shill_service_client_test,
const std::string& service_path)
: property_value_watcher_(shill_service_client_test,
service_path,
shill::kStateProperty) {}
ServiceStateWaiter(const ServiceStateWaiter&) = delete;
ServiceStateWaiter& operator=(const ServiceStateWaiter&) = delete;
void Wait(const std::string& expected_state) {
property_value_watcher_.WaitForValue(expected_state);
}
private:
ServicePropertyValueWatcher property_value_watcher_;
};
// Registers itself as ash::NetworkPolicyObserver and records events for
// ash::NetworkPolicyObserver::PoliciesApplied and
// ash::NetworkPolicyObserver::PolicyAppliedtoNetwork.
class ScopedNetworkPolicyApplicationObserver
: public ash::NetworkPolicyObserver {
public:
ScopedNetworkPolicyApplicationObserver() {
ash::ManagedNetworkConfigurationHandler*
managed_network_configuration_handler =
ash::NetworkHandler::Get()->managed_network_configuration_handler();
managed_network_configuration_handler->AddObserver(this);
}
~ScopedNetworkPolicyApplicationObserver() override {
ash::ManagedNetworkConfigurationHandler*
managed_network_configuration_handler =
ash::NetworkHandler::Get()->managed_network_configuration_handler();
managed_network_configuration_handler->RemoveObserver(this);
}
void PoliciesApplied(const std::string& userhash) override {
policies_applied_events_.push_back(userhash);
policies_applied_wait_loop_[userhash].Quit();
}
void PolicyAppliedToNetwork(const std::string& service_path) override {
policy_applied_to_network_events_.push_back(service_path);
}
const std::vector<std::string>& policies_applied_events() {
return policies_applied_events_;
}
const std::vector<std::string>& policy_applied_to_network_events() {
return policy_applied_to_network_events_;
}
// Clears all recorded events.
void ResetEvents() {
policies_applied_events_.clear();
policy_applied_to_network_events_.clear();
}
void WaitPoliciesApplied(const std::string& userhash) {
policies_applied_wait_loop_[userhash].Run();
}
private:
std::vector<std::string> policies_applied_events_;
std::vector<std::string> policy_applied_to_network_events_;
std::map<std::string, base::RunLoop> policies_applied_wait_loop_;
};
class ScopedNetworkCertLoaderRefreshWaiter
: public ash::NetworkCertLoader::Observer {
public:
ScopedNetworkCertLoaderRefreshWaiter() {
ash::NetworkCertLoader::Get()->AddObserver(this);
}
~ScopedNetworkCertLoaderRefreshWaiter() override {
ash::NetworkCertLoader::Get()->RemoveObserver(this);
}
void OnCertificatesLoaded() override { run_loop_.Quit(); }
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
};
// Allows waiting until a set of GUIDs is available as NetworkStateProperties in
// CrosNetworkConfig.
class CrosNetworkConfigGuidsAvailableWaiter
: chromeos::network_config::CrosNetworkConfigObserver {
public:
CrosNetworkConfigGuidsAvailableWaiter(
ash::network_config::CrosNetworkConfig* cros_network_config,
const std::set<std::string>& expected_guids)
: cros_network_config_(cros_network_config),
expected_guids_(expected_guids) {
// This is a "mojo remote" so it will be automatically removed when
// destroyed.
cros_network_config_->AddObserver(
cros_network_config_observer_receiver_.BindNewPipeAndPassRemote());
}
~CrosNetworkConfigGuidsAvailableWaiter() override = default;
void Wait() {
if (DoAllNetworkStatesExist()) {
return;
}
run_loop_.Run();
}
private:
// Fired when the list of networks changes.
void OnNetworkStateListChanged() override {
if (DoAllNetworkStatesExist()) {
run_loop_.Quit();
}
}
bool DoAllNetworkStatesExist() {
// Use GetNetworkState to check if a NetworkState with `guid_` exists.
base::test::TestFuture<
std::vector<network_mojom::NetworkStatePropertiesPtr>>
network_states_future;
cros_network_config_->GetNetworkStateList(
AcceptEverything(), network_states_future.GetCallback());
std::set<std::string> guids =
NetworkStatesToGuids(network_states_future.Get());
return std::includes(guids.begin(), guids.end(), expected_guids_.begin(),
expected_guids_.end());
}
static network_mojom::NetworkFilterPtr AcceptEverything() {
return network_mojom::NetworkFilter::New(network_mojom::FilterType::kAll,
network_mojom::NetworkType::kAll,
network_mojom::kNoLimit);
}
static std::set<std::string> NetworkStatesToGuids(
const std::vector<network_mojom::NetworkStatePropertiesPtr>&
network_states) {
std::set<std::string> guids;
base::ranges::transform(network_states, std::inserter(guids, guids.begin()),
&network_mojom::NetworkStateProperties::guid);
return guids;
}
base::RunLoop run_loop_;
const raw_ptr<ash::network_config::CrosNetworkConfig> cros_network_config_;
const std::set<std::string> expected_guids_;
// Receiver for the CrosNetworkConfigObserver events.
mojo::Receiver<network_mojom::CrosNetworkConfigObserver>
cros_network_config_observer_receiver_{this};
};
std::string OncPolicyToSelectClientCert(const std::string& guid,
const std::string& ssid,
const std::string& issuer_common_name,
const std::string& identity) {
return base::StringPrintf(R"(
{
"NetworkConfigurations": [
{
"GUID": "%s",
"Name": "OncPolicyToSelectClientCert",
"Type": "WiFi",
"WiFi": {
"EAP": {
"Outer": "EAP-TLS",
"ClientCertType": "Pattern",
"Identity": "%s",
"ClientCertPattern": {
"Issuer": {
"CommonName": "%s"
}
}
},
"SSID": "%s",
"Security": "WPA-EAP"
}
}
]
})",
guid.c_str(), identity.c_str(),
issuer_common_name.c_str(), ssid.c_str());
}
// Returns the configured static IP address from `shill_properties`, or an empty
// string if no static IP address is configured.
std::string GetStaticIPAddressFromShillProperties(
const base::Value::Dict& shill_properties) {
const base::Value::Dict* static_ip_config =
shill_properties.FindDict(shill::kStaticIPConfigProperty);
if (!static_ip_config) {
return std::string();
}
const std::string* address =
static_ip_config->FindString(shill::kAddressProperty);
if (!address) {
return std::string();
}
EXPECT_THAT(*address, Not(IsEmpty()));
return *address;
}
// Returns the configured static name servers from `shill_properties`, or an
// empty vector if no static name servers are configured.
std::vector<std::string> GetStaticNameServersFromShillProperties(
const base::Value::Dict& shill_properties) {
const base::Value::Dict* static_ip_config =
shill_properties.FindDict(shill::kStaticIPConfigProperty);
if (!static_ip_config) {
return {};
}
const base::Value::List* nameservers =
static_ip_config->FindList(shill::kNameServersProperty);
if (!nameservers) {
return {};
}
std::vector<std::string> result;
for (const base::Value& item : *nameservers) {
result.push_back(item.GetString());
}
EXPECT_THAT(result, Not(IsEmpty()));
return result;
}
// Waits until a views::View exists which is a (possibly indirect) child of a
// parent View and satisfies some predicate.
class ViewWaiter : public views::ViewObserver {
public:
// This will be used to check if a view is the one the ViewWaiter is waiting
// for.
using ViewPredicate = base::RepeatingCallback<bool(views::View*)>;
ViewWaiter(views::View* observed_view, ViewPredicate view_predicate)
: observed_view_(observed_view), view_predicate_(view_predicate) {}
void Wait() {
matching_view_ = FindExpectedView(observed_view_);
if (matching_view_ != nullptr) {
return;
}
scoped_observation_.Observe(observed_view_);
run_loop.Run();
if (matching_view_ == nullptr) {
FAIL() << "Could not find the expected view";
}
}
views::View* GetMatchingView() { return matching_view_; }
private:
void OnViewHierarchyChanged(
views::View* observed_view,
const views::ViewHierarchyChangedDetails& details) override {
matching_view_ = FindExpectedView(observed_view_);
if (matching_view_ != nullptr) {
run_loop.Quit();
}
}
views::View* FindExpectedView(views::View* view) {
if (view_predicate_.Run(view)) {
return view;
}
for (views::View* const child : view->children()) {
if (views::View* const found = FindExpectedView(child)) {
return found;
}
}
return nullptr;
}
raw_ptr<views::View> observed_view_;
ViewPredicate view_predicate_;
base::ScopedObservation<views::View, ViewWaiter> scoped_observation_{this};
base::RunLoop run_loop;
raw_ptr<views::View> matching_view_;
};
ViewWaiter::ViewPredicate ViewWithLabel(const std::u16string& label) {
return base::BindLambdaForTesting([label](views::View* view) {
if (views::Label* const label_view = views::AsViewClass<views::Label>(view);
label_view && label_view->GetText() == label) {
return true;
}
return false;
});
}
ViewWaiter::ViewPredicate LabelButtonWithLabel(const std::u16string& label) {
return base::BindLambdaForTesting([label](views::View* view) {
if (views::LabelButton* const label_button_view =
views::AsViewClass<views::LabelButton>(view);
label_button_view && label_button_view->GetText() == label) {
return true;
}
return false;
});
}
// Opens the "network detailed view" of the system tray and attempts to press on
// the entry that is displaying `ssid`. This relies on the fact that the SSID
// will be on the corresponding UI label verbatim.
void ConnectToSsidUsingSystemTray(std::string_view ssid) {
auto system_tray = ash::SystemTrayTestApi::Create();
system_tray->ShowNetworkDetailedView();
ViewWaiter waiter(system_tray->GetMainBubbleView(),
ViewWithLabel(base::UTF8ToUTF16(ssid)));
ASSERT_NO_FATAL_FAILURE(waiter.Wait());
ui::test::EventGenerator event_generator(ash::Shell::GetPrimaryRootWindow());
event_generator.MoveMouseTo(
waiter.GetMatchingView()->GetBoundsInScreen().CenterPoint());
event_generator.ClickLeftButton();
}
void AcceptCertEnrollmentDialog(views::Widget* dialog_widget) {
ViewWaiter waiter(dialog_widget->GetRootView(),
LabelButtonWithLabel(l10n_util::GetStringUTF16(
IDS_NETWORK_ENROLLMENT_HANDLER_BUTTON)));
ASSERT_NO_FATAL_FAILURE(waiter.Wait());
// For some reason, clicking on the button found by `waiter` (which is the
// "Accept" button of the dialog) doesn't seem to have an action in a
// browsertest, so "accept" the dialog programmatically.
dialog_widget->widget_delegate()->AsDialogDelegate()->AcceptDialog();
}
} // namespace
// This class is used for implementing integration tests for network policy
// application across sign-in screen and/or user session.
class NetworkPolicyApplicationTest : public ash::LoginManagerTest {
public:
NetworkPolicyApplicationTest() : LoginManagerTest() {
MarkEnterpriseEnrolled();
login_mixin_.AppendRegularUsers(1);
test_account_id_ = login_mixin_.users()[0].account_id;
}
NetworkPolicyApplicationTest(const NetworkPolicyApplicationTest&) = delete;
NetworkPolicyApplicationTest& operator=(const NetworkPolicyApplicationTest&) =
delete;
protected:
// InProcessBrowserTest:
void SetUp() override {
policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
BrowserPolicyConnector::SetPolicyProviderForTesting(&policy_provider_);
LoginManagerTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
LoginManagerTest::SetUpCommandLine(command_line);
// Allow policy fetches to fail - these tests use
// MockConfigurationPolicyProvider.
command_line->AppendSwitch(ash::switches::kAllowFailedPolicyFetchForTest);
}
void SetUpInProcessBrowserTestFixture() override {
LoginManagerTest::SetUpInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
LoginManagerTest::SetUpOnMainThread();
shill_manager_client_test_ =
ash::ShillManagerClient::Get()->GetTestInterface();
shill_service_client_test_ =
ash::ShillServiceClient::Get()->GetTestInterface();
shill_profile_client_test_ =
ash::ShillProfileClient::Get()->GetTestInterface();
shill_device_client_test_ =
ash::ShillDeviceClient::Get()->GetTestInterface();
shill_service_client_test_->ClearServices();
shill_device_client_test_->ClearDevices();
shill_profile_client_test_->ClearProfiles();
shill_manager_client_test_->SetWifiServicesVisibleByDefault(false);
shill_manager_client_test_->AddTechnology(shill::kTypeWifi,
true /* enabled */);
shill_device_client_test_->AddDevice(kDeviceWifiPath, shill::kTypeWifi,
"stub_wifi_device1");
shill_profile_client_test_->AddProfile(kSharedProfilePath, "");
shill_service_client_test_->ClearServices();
cros_network_config_ =
std::make_unique<ash::network_config::CrosNetworkConfig>();
}
void TearDownOnMainThread() override {
cros_network_config_.reset();
LoginManagerTest::TearDownOnMainThread();
}
void MarkEnterpriseEnrolled() {
stub_install_attributes_.Get()->SetCloudManaged(kTestDomain, kTestDeviceId);
}
// Sets the DeviceEphemeralNetworkPoliciesEnabled policy to `value`.
void SetDeviceEphemeralNetworkPoliciesEnabledPolicy(bool value) {
current_policy_.Set(key::kDeviceEphemeralNetworkPoliciesEnabled,
POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_CLOUD, base::Value(value), nullptr);
policy_provider_.UpdateChromePolicy(current_policy_);
}
// Sets `device_onc_policy_blob` as DeviceOpenNetworkConfiguration device
// policy. If `wait_applied` is true, waits for a
// NetworkPolicyObserver::PoliciesApplied observer call for the device-wide
// network profile.
void SetDeviceOpenNetworkConfiguration(
const std::string& device_onc_policy_blob,
bool wait_applied) {
ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
current_policy_.Set(key::kDeviceOpenNetworkConfiguration,
POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE,
POLICY_SOURCE_CLOUD,
base::Value(device_onc_policy_blob), nullptr);
policy_provider_.UpdateChromePolicy(current_policy_);
if (wait_applied) {
network_policy_application_observer.WaitPoliciesApplied(
/*userhash=*/std::string());
}
}
// Sets `user_onc_policy_blob` as OpenNetworkConfiguration user policy using
// `policy_provider_`. If `wait_applied` is true, waits for a
// NetworkPolicyObserver::PoliciesApplied observer call for the network
// profile for `user_hash`.
void SetUserOpenNetworkConfiguration(const std::string& user_hash,
const std::string& user_onc_policy_blob,
bool wait_applied) {
ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
current_policy_.Set(key::kOpenNetworkConfiguration, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
base::Value(user_onc_policy_blob), nullptr);
policy_provider_.UpdateChromePolicy(current_policy_);
if (wait_applied) {
network_policy_application_observer.WaitPoliciesApplied(user_hash);
}
}
void OnConnectToServiceFailed(base::OnceClosure run_loop_quit_closure,
const std::string& error_name,
const std::string& error_message) {
ADD_FAILURE() << "Connect failed with " << error_name << " - "
<< error_message;
std::move(run_loop_quit_closure).Run();
}
void ConnectToService(const std::string& service_path) {
base::RunLoop run_loop;
ash::ShillServiceClient::Get()->Connect(
dbus::ObjectPath(service_path), run_loop.QuitClosure(),
base::BindOnce(&NetworkPolicyApplicationTest::OnConnectToServiceFailed,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
}
// Imports the certificate and key described by the |cert_filename| and
// |key_filename| files in |source_dir| to the system token (device-wide).
// Then triggers NetworkCertLoader to re-load its certificates cache.
// Should be wrapped in ASSERT_NO_FATAL_FAILURE.
void ImportCert(const base::FilePath& source_dir,
const std::string& cert_filename,
const std::string& key_filename) {
// Before importing, configure NetworkCertLoader to assume that all
// certificates can be used for network authentication.
ash::NetworkCertLoader::Get()->ForceAvailableForNetworkAuthForTesting();
net::ScopedCERTCertificate cert;
// Import testing key pair and certificate.
{
base::ScopedAllowBlockingForTesting allow_io;
net::ImportClientCertAndKeyFromFile(
source_dir, cert_filename, key_filename,
system_nss_key_slot_mixin_.slot(), &cert);
}
ASSERT_TRUE(cert);
// Trigger refreshing the NetworkCertLoader's cache so the certificate
// becomes available for networks. Production code does this through
// NSSCertDatabase::ImportUserCert.
ScopedNetworkCertLoaderRefreshWaiter network_cert_loader_refresh_waiter;
net::CertDatabase::GetInstance()->NotifyObserversClientCertStoreChanged();
network_cert_loader_refresh_waiter.Wait();
}
// Applies `properties` to the network identified by `guid` using
// CrosNetworkConfig.
void CrosNetworkConfigSetProperties(
const std::string& guid,
network_mojom::ConfigPropertiesPtr properties) {
base::test::TestFuture<bool, std::string> set_properties_future;
cros_network_config_->SetProperties(
guid, std::move(properties),
set_properties_future.GetCallback<bool, const std::string&>());
ASSERT_TRUE(set_properties_future.Wait());
ASSERT_TRUE(set_properties_future.Get<bool>())
<< "Error msg: " << set_properties_future.Get<std::string>();
}
// Retrieves the "managed properties" of the network identified by `guid`
// using CrosNetworkConfig.
network_mojom::ManagedPropertiesPtr CrosNetworkConfigGetManagedProperties(
const std::string& guid) {
base::test::TestFuture<network_mojom::ManagedPropertiesPtr>
get_managed_properties_future;
cros_network_config_->GetManagedProperties(
guid, get_managed_properties_future.GetCallback());
return get_managed_properties_future.Take();
}
network_mojom::GlobalPolicyPtr CrosNetworkConfigGetGlobalPolicy() {
base::test::TestFuture<network_mojom::GlobalPolicyPtr> global_policy_ptr;
cros_network_config_->GetGlobalPolicy(global_policy_ptr.GetCallback());
return global_policy_ptr.Take();
}
network_mojom::NetworkStatePropertiesPtr
CrosNetworkConfigGetNetworkStateProps(const std::string& guid) {
base::test::TestFuture<network_mojom::NetworkStatePropertiesPtr>
network_state_props_ptr;
cros_network_config_->GetNetworkState(
guid, network_state_props_ptr.GetCallback());
return network_state_props_ptr.Take();
}
network_mojom::NetworkCertificatePtr CrosNetworkConfigFindClientCert(
const std::string& ui_name) {
ash::network_config::CrosNetworkConfig cros_network_config;
base::test::TestFuture<std::vector<network_mojom::NetworkCertificatePtr>,
std::vector<network_mojom::NetworkCertificatePtr>>
future;
cros_network_config.GetNetworkCertificates(future.GetCallback());
std::vector<network_mojom::NetworkCertificatePtr> client_certs =
std::get<1>(future.Take());
for (auto& client_cert : client_certs) {
if (client_cert->issued_to == ui_name) {
return std::move(client_cert);
}
}
return {};
}
// Extracts the UIData dictionary from the shill UIData property of the
// service `service_path`.
std::optional<base::Value::Dict> GetUIDataDict(
const std::string& service_path) {
const base::Value::Dict* properties =
shill_service_client_test_->GetServiceProperties(service_path);
if (!properties)
return {};
const std::string* ui_data_json =
properties->FindString(shill::kUIDataProperty);
if (!ui_data_json)
return {};
std::optional<base::Value> ui_data_value =
base::JSONReader::Read(*ui_data_json);
if (!ui_data_value || !ui_data_value->is_dict())
return {};
return std::move(*ui_data_value).TakeDict();
}
// Sets the shill UIData property of the service `service_path` to the
// serialized `ui_data_dict`.
void SetUIDataDict(const std::string& service_path,
const base::Value::Dict& ui_data_dict) {
std::string ui_data_json;
base::JSONWriter::Write(ui_data_dict, &ui_data_json);
shill_service_client_test_->SetServiceProperty(
service_path, shill::kUIDataProperty, base::Value(ui_data_json));
}
// Returns the GUID from the "user_settings" from `ui_data` or an empty string
// of no "user_settings" or no GUID was found.
std::string GetGUIDFromUIData(const base::Value::Dict& ui_data) {
const base::Value::Dict* user_settings =
ui_data.FindDict(kUIDataKeyUserSettings);
if (!user_settings)
return std::string();
const std::string* guid =
user_settings->FindString(::onc::network_config::kGUID);
if (!guid)
return std::string();
return *guid;
}
const base::Value::Dict* GetWifiProps(const std::string& guid) {
std::optional<std::string> wifi_service;
wifi_service = shill_service_client_test_->FindServiceMatchingGUID(guid);
if (wifi_service->empty()) {
ADD_FAILURE() << "No wifi service found for: " << guid;
}
return shill_service_client_test_->GetServiceProperties(
wifi_service.value());
}
void SetServiceVisibility(const std::string& guid, bool visible) {
std::optional<std::string> wifi_service;
wifi_service = shill_service_client_test_->FindServiceMatchingGUID(guid);
if (wifi_service->empty()) {
ADD_FAILURE() << "No wifi service found for: " << guid;
return;
}
shill_service_client_test_->SetServiceProperty(
wifi_service.value(), shill::kVisibleProperty, base::Value(visible));
}
const std::string GetTestUserHash() {
return user_manager::UserManager::Get()
->FindUser(test_account_id_)
->username_hash();
}
bool IsProhibitedByPolicyInCrosNetworkConfig(const std::string& guid) {
return CrosNetworkConfigGetNetworkStateProps(guid)->prohibited_by_policy;
}
const std::string GetWifiStateFromShillClient(const std::string& guid) {
const base::Value::Dict* wifi_properties = GetWifiProps(guid);
const std::string* wifi_state =
wifi_properties->FindString(shill::kStateProperty);
if (!wifi_state) {
ADD_FAILURE() << "Network has no WiFi state properties: " << guid;
return "";
}
return *wifi_state;
}
void AddPskWifiService(const std::string& service_path,
const std::string& initial_guid,
const std::string& ssid,
const std::string& initial_state) {
shill_service_client_test_->AddService(service_path, initial_guid, ssid,
shill::kTypeWifi, initial_state,
/*visible=*/true);
shill_service_client_test_->SetServiceProperty(
service_path, shill::kSSIDProperty, base::Value(ssid));
shill_service_client_test_->SetServiceProperty(
service_path, shill::kSecurityClassProperty,
base::Value(shill::kSecurityClassPsk));
}
void Add8021xWifiService(const std::string& service_path,
const std::string& initial_guid,
const std::string& ssid,
const std::string& initial_state) {
shill_service_client_test_->AddService(service_path, initial_guid, ssid,
shill::kTypeWifi, initial_state,
/*visible=*/true);
shill_service_client_test_->SetServiceProperty(
service_path, shill::kSSIDProperty, base::Value(ssid));
shill_service_client_test_->SetServiceProperty(
service_path, shill::kSecurityClassProperty,
base::Value(shill::kSecurityClass8021x));
}
// Adds a pre-existing PEAP-MSCHAPv2 service into the shared profile that is
// marked as originating from device policy.
void AddSharedDevicePolicyMschapv2Service(
const std::string& service_path,
const std::string& initial_guid,
const std::string& ssid,
const std::string& initial_identity) {
Add8021xWifiService(service_path, initial_guid, "wifi1", shill::kStateIdle);
shill_profile_client_test_->AddService(kSharedProfilePath, service_path);
shill_service_client_test_->SetServiceProperty(
service_path, shill::kEapMethodProperty, base::Value("PEAP"));
shill_service_client_test_->SetServiceProperty(
service_path, shill::kEapIdentityProperty,
base::Value(initial_identity));
shill_service_client_test_->SetServiceProperty(
service_path, shill::kUIDataProperty,
base::Value(R"({"onc_source":"device_policy"})"));
}
void SimulateWifiScanCompleted() {
shill_device_client_test_->SetDeviceProperty(
kDeviceWifiPath, shill::kScanningProperty, base::Value(true),
/*notify_changed=*/true);
base::RunLoop().RunUntilIdle();
shill_device_client_test_->SetDeviceProperty(
kDeviceWifiPath, shill::kScanningProperty, base::Value(false),
/*notify_changed=*/true);
}
// Unowned pointers -- just pointers to the singleton instances.
raw_ptr<ash::ShillManagerClient::TestInterface, DanglingUntriaged>
shill_manager_client_test_ = nullptr;
raw_ptr<ash::ShillServiceClient::TestInterface, DanglingUntriaged>
shill_service_client_test_ = nullptr;
raw_ptr<ash::ShillProfileClient::TestInterface, DanglingUntriaged>
shill_profile_client_test_ = nullptr;
raw_ptr<ash::ShillDeviceClient::TestInterface, DanglingUntriaged>
shill_device_client_test_ = nullptr;
ash::ScopedStubInstallAttributes stub_install_attributes_;
AccountId test_account_id_;
std::unique_ptr<ash::network_config::CrosNetworkConfig> cros_network_config_;
private:
ash::ScopedTestSystemNSSKeySlotMixin system_nss_key_slot_mixin_{&mixin_host_};
ash::LoginManagerMixin login_mixin_{&mixin_host_};
testing::NiceMock<MockConfigurationPolicyProvider> policy_provider_;
PolicyMap current_policy_;
};
// A variant of NetworkPolicyApplicationTest which enables the
// EphemeralNetworkPolicies feature.
class NetworkPolicyApplicationEphemeralActionsEnabledTest
: public NetworkPolicyApplicationTest {
public:
NetworkPolicyApplicationEphemeralActionsEnabledTest() {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kEphemeralNetworkPolicies);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// A variant of NetworkPolicyApplicationTest which disables the
// EphemeralNetworkPolicies feature.
class NetworkPolicyApplicationEphemeralActionsDisabledTest
: public NetworkPolicyApplicationTest {
public:
NetworkPolicyApplicationEphemeralActionsDisabledTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{ash::features::
kEphemeralNetworkPoliciesEnabledPolicy},
/*disabled_features=*/{ash::features::kEphemeralNetworkPolicies});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// A variant of NetworkPolicyApplicationTest which enables the
// EphemeralNetworkPolicies feature and the device is not enterprise enrolled at
// startup.
class NetworkPolicyApplicationEphemeralActionsEnabledUnenrolledTest
: public NetworkPolicyApplicationTest {
public:
NetworkPolicyApplicationEphemeralActionsEnabledUnenrolledTest() {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kEphemeralNetworkPolicies);
stub_install_attributes_.Get()->Clear();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// A variant of NetworkPolicyApplicationTest which disables the
// EphemeralNetworkPolicies and EphemeralNetworkPoliciesEnabledPolicy feature.
class NetworkPolicyApplicationEphemeralActionsKillSwitchTest
: public NetworkPolicyApplicationTest {
public:
NetworkPolicyApplicationEphemeralActionsKillSwitchTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/{
ash::features::kEphemeralNetworkPolicies,
ash::features::kEphemeralNetworkPoliciesEnabledPolicy});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// This test applies a global network policy with
// AllowOnlyPolicyNetworksToAutoconnect set to true. It then performs a user
// log-in and simulates that user policy application is slow. This is a
// regression test for https://crbug.com/936677.
// Specifically, it simulates that:
// 1. ash-chrome applies device network policy in shill.
// The device policy mandates that only policy configured networks may
// auto-connect.
// 2. The user manually connects to a non-policy-managed network
// 3. The user signs in and ash-chrome applies user network policy in shill.
// Important:
// shill does not reflect the property changes back to chrome through
// D-Bus PropertyChanged events yet.
// In the test, this is simulated by
// shill_service_client_test_->SetHoldBackServicePropertyUpdates(true);
// In this case, the signal that policies have been applied yet may not be
// triggered yet.
// Only after shill is allowed to send PropertyChanged events to chrome will
// chrome's data models be updated, and then the "policies applied" signal
// should be triggered.
//
// This is checked in the test in two ways:
// - Direct observation of ash::NetworkPolicyObserver through
// ScopedNetworkPolicyApplicationObserver.
// - Checking that AutoConnectHandler didn't disconnect the manually-connected
// network, which was an observable consequence of the bug in this setup.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
OnlyPolicyAutoconnectWithSlowUserPolicyApplication) {
ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
// Set up two services.
AddPskWifiService(kServiceWifi1, "wifi_orig_guid_1", "WifiOne",
shill::kStateIdle);
AddPskWifiService(kServiceWifi2, "wifi_orig_guid_2", "WifiTwo",
shill::kStateIdle);
// Apply device ONC policy and wait until it takes effect (one of the networks
// auto connects).
const char kDeviceONC[] = R"(
{
"GlobalNetworkConfiguration": {
"AllowOnlyPolicyNetworksToAutoconnect": true,
"AllowOnlyPolicyNetworksToConnect": false
},
"NetworkConfigurations": [
{
"GUID": "{device-policy-for-Wifi1}",
"Name": "DeviceLevelWifi",
"Type": "WiFi",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "WifiOne",
"Security": "WPA-PSK"
}
}
]
})";
ServiceStateWaiter wifi_one_connected_waiter(shill_service_client_test_,
kServiceWifi1);
shill_manager_client_test_->SetBestServiceToConnect(kServiceWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
wifi_one_connected_waiter.Wait(shill::kStateOnline);
EXPECT_THAT(
network_policy_application_observer.policy_applied_to_network_events(),
ElementsAre(kServiceWifi1));
EXPECT_THAT(network_policy_application_observer.policies_applied_events(),
ElementsAre(std::string() /* shill shared profile */));
network_policy_application_observer.ResetEvents();
std::optional<std::string> wifi_service =
shill_service_client_test_->FindServiceMatchingGUID(
"{device-policy-for-Wifi1}");
ASSERT_TRUE(wifi_service);
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(wifi_service.value());
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(
*wifi_service_properties,
DictionaryHasValue(shill::kAutoConnectProperty, base::Value(true)));
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kProfileProperty,
base::Value(kSharedProfilePath)));
}
// Manually connect to the other network.
ConnectToService(kServiceWifi2);
// Sign-in a user and apply user ONC policy. Simulate that shill takes a while
// to reflect the changes back to chrome by holding back service property
// updates (regression test for https://crbug.com/936677).
shill_service_client_test_->SetHoldBackServicePropertyUpdates(true);
LoginUser(test_account_id_);
const std::string user_hash = user_manager::UserManager::Get()
->FindUser(test_account_id_)
->username_hash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
// When AutoConnectHandler triggers ScanAndConnectToBestServices, shill should
// not do anything for now. This allows us to test whether AutoConnectHandler
// is explicitly disconnecting networks.
shill_manager_client_test_->SetBestServiceToConnect(std::string());
const char kUserONC[] = R"(
{
"NetworkConfigurations": [
{
"GUID": "{user-policy-for-Wifi1}",
"Name": "DeviceLevelWifi",
"Type": "WiFi",
"WiFi": {
"AutoConnect": false,
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "WifiOne",
"Security": "WPA-PSK"
}
},
{
"GUID": "{user-policy-for-Wifi2}",
"Name": "UserLevelWifi",
"Type": "WiFi",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"Passphrase": "UserLevelWifiPwd",
"SSID": "WifiTwo",
"Security": "WPA-PSK"
}
}
]
})";
SetUserOpenNetworkConfiguration(user_hash, kUserONC, /*wait_applied=*/false);
// Expect that the policies have not been signalled as applied yet because
// property updates are being held back by FakeShillServiceClient.
EXPECT_TRUE(
network_policy_application_observer.policy_applied_to_network_events()
.empty());
EXPECT_TRUE(
network_policy_application_observer.policies_applied_events().empty());
// Now let fake shill reflect the property updates, so policy application is
// marked as done.
shill_service_client_test_->SetHoldBackServicePropertyUpdates(false);
network_policy_application_observer.WaitPoliciesApplied(user_hash);
EXPECT_THAT(
network_policy_application_observer.policy_applied_to_network_events(),
ElementsAre(kServiceWifi1, kServiceWifi2));
// Expect that the same service path now has the user policy GUID.
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(wifi_service.value());
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kGuidProperty,
base::Value("{user-policy-for-Wifi1}")));
EXPECT_THAT(
*wifi_service_properties,
DictionaryHasValue(shill::kAutoConnectProperty, base::Value(false)));
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kProfileProperty,
base::Value(kUserProfilePath)));
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kStateProperty,
base::Value(shill::kStateIdle)));
}
std::optional<std::string> wifi2_service =
shill_service_client_test_->FindServiceMatchingGUID(
"{user-policy-for-Wifi2}");
ASSERT_TRUE(wifi2_service);
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(wifi2_service.value());
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(
*wifi_service_properties,
DictionaryHasValue(shill::kAutoConnectProperty, base::Value(true)));
// This service is still connected. This is an important check in this
// regression test:
// In https://crbug.com/936677, AutoConnectHandler was already running
// (because OnPoliciesApplied was already triggered) when the NetworkState
// for a policy-managed network was not marked managed yet (because shill
// has not reflected the property changes yet). As a consequence,
// AutoConnectHandler disconnected the current network because of the global
// AllowOnlyPolicyNetworksToAutoconnect policy. Verify that this has not
// happened in this test.
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kStateProperty,
base::Value(shill::kStateOnline)));
}
}
// Verify that AllowOnlyPolicyNetworksToConnect is working correctly , so
// non-policy-managed networks can be connected before user's login, but
// they will be automatically disconnected after the user's login and they also
// will be forbidden in CrosNetworkConfig.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
OnlyPolicyAllowedNetworkCanBeConected) {
ScopedNetworkPolicyApplicationObserver policy_observer;
ash::ShillServiceClient::TestInterface* shill_client =
shill_service_client_test_;
constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
constexpr char kGuidWifi2[] = "wifi_orig_guid_2";
constexpr char kGuidWifi3[] = "wifi_orig_guid_3";
constexpr char kGuidWifi4[] = "wifi_orig_guid_4";
// Network 1 will be managed by device policy and it will have auto connect.
// Network 2 will be managed by user policy.
// Network 3 and 4 will be not managed. Network 4 is added just to make sure
// that we have more than one un-managed network.
CrosNetworkConfigGuidsAvailableWaiter available_waiter(
cros_network_config_.get(),
{kGuidWifi1, kGuidWifi2, kGuidWifi3, kGuidWifi4});
AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
AddPskWifiService(kServiceWifi3, kGuidWifi3, "WifiThree", shill::kStateIdle);
AddPskWifiService(kServiceWifi4, kGuidWifi4, "WifiFour", shill::kStateIdle);
available_waiter.Wait();
// Check that initially no policies applied and CrosNetworkStateProperties
// has no prohibited networks.
{
EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect);
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi3));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi4));
}
// Apply device ONC policy and wait until it takes effect (1st network
// start auto connects). Those setting are not effective before user's login,
// but we can check that CrosNetworkStateProperties are updated correctly.
{
const char kDeviceONC[] = R"(
{
"GlobalNetworkConfiguration": {
"AllowOnlyPolicyNetworksToConnect": true
},
"NetworkConfigurations": [
{
"GUID": "wifi_orig_guid_1",
"Name": "DeviceLevelWifi",
"Type": "WiFi",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "WifiOne",
"Security": "WPA-PSK"
}
}
]
})";
ServiceStateWaiter wifi_one_connected_waiter(shill_client, kServiceWifi1);
shill_manager_client_test_->SetBestServiceToConnect(kServiceWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
wifi_one_connected_waiter.Wait(shill::kStateOnline);
}
// Check before login that device can connect to any available network.
// This is because AllowOnlyPolicyNetworksToConnect only applies in user's
// sessions, even though it is a device-wide network policy.
// WiFi3 will be kept online and it should be disconnected automatically after
// the login because policy will take action, WiFi1 will become connected
// after the login because it has "autoconnect".
{
// Verify that GlobalPolicy from CrosNetworkConfig will allow connection
// only to networks defined and all networks are not prohibited.
EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect);
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi3));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi4));
// Check that device is connected to WiFi1 while other networks (2,3,4)
// are "idle".
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);
// Manually connect to the 2nd network and verify that it is "online"
// while network 1, 3, 4 are "idle".
ConnectToService(kServiceWifi2);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);
// Manually connect to the 3d network and verify that it is "online"
// network 1, 2, 4 are "idle".
ConnectToService(kServiceWifi3);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);
}
// Sign-in a user and apply user ONC policy for WiFi2.
{
LoginUser(test_account_id_);
const std::string user_hash = GetTestUserHash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
const char kUserONC[] = R"(
{
"NetworkConfigurations": [
{
"GUID": "wifi_orig_guid_2",
"Name": "UserLevelWifi",
"Type": "WiFi",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"Passphrase": "UserLevelWifiPwd",
"SSID": "WifiTwo",
"Security": "WPA-PSK"
}
}
]
})";
SetUserOpenNetworkConfiguration(user_hash, kUserONC,
/*wait_applied=*/true);
}
// Verify that WiFi3 is disconnected as it is not included into device or
// into user's policies. Also now WiFi3 and WiFi4 are prohibited
// by policy. WiFi1 should be connected (online) because it has auto connect.
{
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi3), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi4), shill::kStateIdle);
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi3));
EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi4));
}
}
// Tests behavior of BlockedHexSSIDs on login screen and in a user session.
// Also tests that if a blocked SSID was connected on the sign-in screen, it is
// disconnected when a user signs in.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, BlockedHexSSIDs) {
constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
constexpr char kGuidWifi2[] = "wifi_orig_guid_2";
CrosNetworkConfigGuidsAvailableWaiter available_waiter(
cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
available_waiter.Wait();
// Check that initially no policies applied and CrosNetworkStateProperties
// has no prohibited networks.
{
EXPECT_THAT(CrosNetworkConfigGetGlobalPolicy()->blocked_hex_ssids,
IsEmpty());
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
}
// Apply device ONC policy.
// 576966694F6E65 is hex-encoded ASCII "WifiOne"
{
const char kDeviceONC[] = R"(
{
"GlobalNetworkConfiguration": {
"BlockedHexSSIDs": [ "576966694F6E65" ]
},
"NetworkConfigurations": [ ]
})";
SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
EXPECT_THAT(CrosNetworkConfigGetGlobalPolicy()->blocked_hex_ssids,
ElementsAre("576966694F6E65"));
}
// The network is still connectable because BlockedHexSSIDs is not applied on
// the sign-in screen.
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
ConnectToService(kServiceWifi1);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
// Sign-in a user and apply user without ONC policy.
// The blocked network should be automatically disconnected.
{
ServiceStateWaiter wifi_disconnected_waiter(shill_service_client_test_,
kServiceWifi1);
LoginUser(test_account_id_);
const std::string user_hash = GetTestUserHash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
wifi_disconnected_waiter.Wait(shill::kStateIdle);
}
EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
// TODO(b/277809215): Attempt to connect to the prohibited service using
// CrosNetworkConfig.
}
// Behavior of AllowOnlyPolicyNetworksToConnectIfAvailable when no policy
// networks are configured.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
OnlyPolicyIfAvailable_NotConfigured) {
constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
constexpr char kGuidWifi2[] = "wifi_orig_guid_2";
CrosNetworkConfigGuidsAvailableWaiter available_waiter(
cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
available_waiter.Wait();
// Check that initially no policies applied and CrosNetworkStateProperties
// has no prohibited networks.
{
EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect);
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
}
// Apply device ONC policy
{
const char kDeviceONC[] = R"(
{
"GlobalNetworkConfiguration": {
"AllowOnlyPolicyNetworksToConnectIfAvailable": true
},
"NetworkConfigurations": [ ]
})";
SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect_if_available);
}
// Manually connecting to a non-policy network must be possible.
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
ConnectToService(kServiceWifi1);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
// Sign-in a user and apply user without ONC policy.
{
LoginUser(test_account_id_);
const std::string user_hash = GetTestUserHash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
}
// WiFi1 should still be connected.
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
}
// Behavior of AllowOnlyPolicyNetworksToConnectIfAvailable when no policy
// networks are visible initially, then after user login user login, a
// policy-provided network becomes visible. After that the policy-provided
// network becomes not visible again.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
OnlyPolicyIfAvailable_VisibleBackAndForth) {
constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
constexpr char kGuidWifi2[] = "wifi_orig_guid_2";
CrosNetworkConfigGuidsAvailableWaiter available_waiter(
cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
available_waiter.Wait();
// Check that initially no policies applied and CrosNetworkStateProperties
// has no prohibited networks.
{
EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect);
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
}
// Apply device ONC policy
{
const char kDeviceONC[] = R"(
{
"GlobalNetworkConfiguration": {
"AllowOnlyPolicyNetworksToConnectIfAvailable": true
},
"NetworkConfigurations": [
{
"GUID": "wifi_policy_1",
"Name": "PolicyDeviceLevelWifi",
"Type": "WiFi",
"WiFi": {
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "PolicyDeviceLevelWifi",
"Security": "WPA-PSK"
}
}
]
})";
SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect_if_available);
}
// Manually connecting to a non-policy network must be possible.
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
ConnectToService(kServiceWifi1);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);
// Sign-in a user and apply user ONC policy for another unavailable network.
{
LoginUser(test_account_id_);
const std::string user_hash = GetTestUserHash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
const char kUserONC[] = R"(
{
"NetworkConfigurations": [
{
"GUID": "wifi_policy_2",
"Name": "UserLevelWifi",
"Type": "WiFi",
"WiFi": {
"HiddenSSID": false,
"Passphrase": "UserLevelWifiPwd",
"SSID": "PolicyUserLevelWifi",
"Security": "WPA-PSK"
}
}
]
})";
SetUserOpenNetworkConfiguration(user_hash, kUserONC,
/*wait_applied=*/true);
}
// WiFi1 should still be connected.
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_2"));
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_2"), shill::kStateIdle);
{
// Now the policy-provided network becomes visible in a wifi scan.
// Expect that wifi_policy_2 connects.
std::optional<std::string> user_policy_wifi_service_path =
shill_service_client_test_->FindServiceMatchingGUID("wifi_policy_2");
ASSERT_TRUE(user_policy_wifi_service_path);
ServiceStateWaiter wifi_connected_waiter(
shill_service_client_test_, user_policy_wifi_service_path.value());
SetServiceVisibility("wifi_policy_2", true);
SimulateWifiScanCompleted();
wifi_connected_waiter.Wait(shill::kStateOnline);
}
// Expects that the non-policy WiFi services are now prohibited and that the
// policy-provided network has connected.
EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_2"));
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_2"), shill::kStateOnline);
// Now the policy-provided network becomes invisible again, and no network is
// prohibited anymore.
SetServiceVisibility("wifi_policy_2", false);
SimulateWifiScanCompleted();
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_2"));
}
// Behavior of AllowOnlyPolicyNetworksToConnectIfAvailable when a device policy
// network is visible on user login.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
OnlyPolicyIfAvailable_DeviceNetworkVisibleOnLogin) {
constexpr char kGuidWifi1[] = "wifi_orig_guid_1";
constexpr char kGuidWifi2[] = "wifi_orig_guid_2";
CrosNetworkConfigGuidsAvailableWaiter available_waiter(
cros_network_config_.get(), {kGuidWifi1, kGuidWifi2});
AddPskWifiService(kServiceWifi1, kGuidWifi1, "WifiOne", shill::kStateIdle);
AddPskWifiService(kServiceWifi2, kGuidWifi2, "WifiTwo", shill::kStateIdle);
available_waiter.Wait();
// Check that initially no policies applied and CrosNetworkStateProperties
// has no prohibited networks.
{
EXPECT_FALSE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect);
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
}
// Apply device ONC policy
{
const char kDeviceONC[] = R"(
{
"GlobalNetworkConfiguration": {
"AllowOnlyPolicyNetworksToConnectIfAvailable": true
},
"NetworkConfigurations": [
{
"GUID": "wifi_policy_1",
"Name": "PolicyDeviceLevelWifi",
"Type": "WiFi",
"WiFi": {
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "PolicyDeviceLevelWifi",
"Security": "WPA-PSK"
}
}
]
})";
SetDeviceOpenNetworkConfiguration(kDeviceONC, /*wait_applied=*/true);
EXPECT_TRUE(CrosNetworkConfigGetGlobalPolicy()
->allow_only_policy_wifi_networks_to_connect_if_available);
}
// Make the device policy network visible.
SetServiceVisibility("wifi_policy_1", true);
// Manually connecting to a non-policy network must be possible (this is still
// on the sign-in screen).
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
ConnectToService(kServiceWifi1);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi1), shill::kStateOnline);
EXPECT_EQ(GetWifiStateFromShillClient(kGuidWifi2), shill::kStateIdle);
EXPECT_EQ(GetWifiStateFromShillClient("wifi_policy_1"), shill::kStateIdle);
// Sign-in a user. The device policy network should connect because the
// AllowOnlyPolicyNetworksToConnectIfAvailable became effective on user login.
{
std::optional<std::string> policy_wifi_service_path =
shill_service_client_test_->FindServiceMatchingGUID("wifi_policy_1");
ASSERT_TRUE(policy_wifi_service_path);
ServiceStateWaiter wifi_connected_waiter(shill_service_client_test_,
policy_wifi_service_path.value());
shill_manager_client_test_->SetBestServiceToConnect(
policy_wifi_service_path.value());
LoginUser(test_account_id_);
const std::string user_hash = GetTestUserHash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
wifi_connected_waiter.Wait(shill::kStateOnline);
}
// Expects that the non-policy WiFi services are now prohibited.
EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi1));
EXPECT_TRUE(IsProhibitedByPolicyInCrosNetworkConfig(kGuidWifi2));
EXPECT_FALSE(IsProhibitedByPolicyInCrosNetworkConfig("wifi_policy_1"));
}
// Checks the edge case where a policy with GUID {same_guid} applies to network
// with SSID "WifiTwo", and subsequently the policy changes, the new
// NetworkConfiguration with GUID {same_guid} now applying to SSID "WifiOne".
// For this to work correctly, PolicyApplicator must first clear the "WifiTwo"
// settings so it is not matched by GUID, and then write the new policy to
// shill.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
PolicyWithSameGUIDAppliesToOtherSSID) {
// Set up two services.
AddPskWifiService(kServiceWifi1, "wifi_orig_guid_1", "WifiOne",
shill::kStateIdle);
AddPskWifiService(kServiceWifi2, "wifi_orig_guid_2", "WifiTwo",
shill::kStateIdle);
const char kDeviceONC1[] = R"(
{
"NetworkConfigurations": [
{
"GUID": "{same_guid}",
"Name": "X",
"Type": "WiFi",
"WiFi": {
"AutoConnect": false,
"HiddenSSID": false,
"Passphrase": "Passphrase",
"SSID": "WifiTwo",
"Security": "WPA-PSK"
}
}
]
})";
SetDeviceOpenNetworkConfiguration(kDeviceONC1, /*wait_applied=*/true);
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi2);
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(
*wifi_service_properties,
DictionaryHasValue(shill::kGuidProperty, base::Value("{same_guid}")));
}
// Same GUID for a different SSID.
const char kDeviceONC2[] = R"(
{
"NetworkConfigurations": [
{
"GUID": "{same_guid}",
"Name": "X",
"Type": "WiFi",
"WiFi": {
"AutoConnect": false,
"HiddenSSID": false,
"Passphrase": "SomePassphrase",
"SSID": "WifiOne",
"Security": "WPA-PSK"
}
}
]
})";
SetDeviceOpenNetworkConfiguration(kDeviceONC2, /*wait_applied=*/true);
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi2);
ASSERT_TRUE(wifi_service_properties);
EXPECT_FALSE(wifi_service_properties->Find(shill::kGuidProperty));
}
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(
*wifi_service_properties,
DictionaryHasValue(shill::kGuidProperty, base::Value("{same_guid}")));
}
}
// Tests that application of policy settings does not wipe an already-configured
// client certificate. This is a regression test for b/203015922.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, DoesNotWipeCertSettings) {
const char* kCertKeyFilename = "client_3.pk8";
const char* kCertFilename = "client_3.pem";
const char* kCertIssuerCommonName = "E CA";
ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
kCertFilename, kCertKeyFilename));
// Set up a policy-managed EAP wifi with a certificate already selected.
Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
"DeviceLevelWifiSsid", shill::kStateOnline);
ServicePropertyValueWatcher eap_cert_id_watcher(
shill_service_client_test_, kServiceWifi1, shill::kEapCertIdProperty);
ServicePropertyValueWatcher eap_key_id_watcher(
shill_service_client_test_, kServiceWifi1, shill::kEapKeyIdProperty);
ServicePropertyValueWatcher eap_identity_watcher(
shill_service_client_test_, kServiceWifi1, shill::kEapIdentityProperty);
SetDeviceOpenNetworkConfiguration(
OncPolicyToSelectClientCert("DeviceLevelWifiGuid", "DeviceLevelWifiSsid",
/*issuer_common_name=*/kCertIssuerCommonName,
/*identity=*/"identity_1"),
/*wait_applied=*/true);
// Verify that the EAP.CertId and EAP.KeyId properties are present and not
// empty, i.e. that a client certificate has been selected.
ASSERT_THAT(eap_cert_id_watcher.GetValues(), ElementsAre(Not(IsEmpty())));
ASSERT_THAT(eap_key_id_watcher.GetValues(), ElementsAre(Not(IsEmpty())));
std::string orig_eap_cert_id = eap_cert_id_watcher.GetValues().back();
std::string orig_eap_key_id = eap_key_id_watcher.GetValues().back();
EXPECT_THAT(eap_identity_watcher.GetValues(), ElementsAre("identity_1"));
SetDeviceOpenNetworkConfiguration(
OncPolicyToSelectClientCert("DeviceLevelWifiGuid", "DeviceLevelWifiSsid",
/*issuer_common_name=*/kCertIssuerCommonName,
/*identity=*/"identity_2"),
/*wait_applied=*/true);
// Verify that the EAP.CertId and EAP.KeyId properties have not been changed
// to anything else (also not an empty string).
ASSERT_THAT(eap_cert_id_watcher.GetValues(), ElementsAre(orig_eap_cert_id));
ASSERT_THAT(eap_key_id_watcher.GetValues(), ElementsAre(orig_eap_key_id));
EXPECT_THAT(eap_identity_watcher.GetValues(),
ElementsAre("identity_1", "identity_2"));
}
// Configures a device-wide network that uses variable expansions
// (https://chromium.googlesource.com/chromium/src/+/main/components/onc/docs/onc_spec.md#string-expansions)
// and then tests that these variables are replaced with their values in the
// config pushed to shill.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
DevicePolicyProfileWideVariableExpansions) {
const std::string kSerialNumber = "test_serial";
ash::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
fake_statistics_provider_.SetMachineStatistic(ash::system::kSerialNumberKey,
kSerialNumber);
Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
"DeviceLevelWifiSsid", shill::kStateIdle);
SetDeviceOpenNetworkConfiguration(
OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
"DeviceLevelWifiSsid",
/*issuer_common_name=*/"Example Inc.",
/*identity=*/"${DEVICE_SERIAL_NUMBER}"),
/*wait_applied=*/true);
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kGuidProperty,
base::Value("{DeviceLevelWifiGuid}")));
// Expect that the EAP.Identity has been replaced
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kEapIdentityProperty,
base::Value(kSerialNumber)));
// TODO(b/209084821): Also test DEVICE_ASSET_ID when it's easily
// configurable in a browsertest.
}
}
// Configures a network that uses variable expansions with variables based on a
// client certificate selected using a CertificatePattern.
// The network is device-wide because that is easier to set up in the test.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
DevicePolicyCertBasedVariableExpansions) {
const char* kCertKeyFilename = "client_3.pk8";
const char* kCertFilename = "client_3.pem";
const char* kCertIssuerCommonName = "E CA";
const char* kIdentityPolicyValue =
"${CERT_SUBJECT_COMMON_NAME}/${CERT_SAN_UPN}/${CERT_SAN_EMAIL}";
const char* kExpectedIdentity =
"Client Cert F/[email protected]/[email protected]";
ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
kCertFilename, kCertKeyFilename));
Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
"DeviceLevelWifiSsid", shill::kStateIdle);
SetDeviceOpenNetworkConfiguration(
OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
"DeviceLevelWifiSsid",
/*issuer_common_name=*/kCertIssuerCommonName,
/*identity=*/kIdentityPolicyValue),
/*wait_applied=*/true);
ServicePropertyValueWatcher(shill_service_client_test_, kServiceWifi1,
shill::kEapCertIdProperty)
.WaitForNonEmptyValue();
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kGuidProperty,
base::Value("{DeviceLevelWifiGuid}")));
// Expect that the EAP.Identity has been replaced
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kEapIdentityProperty,
base::Value(kExpectedIdentity)));
}
}
// Configures a user-specific network that uses variable expansions
// (https://chromium.googlesource.com/chromium/src/+/main/components/onc/docs/onc_spec.md#string-expansions)
// and then tests that these variables are replaced with their values in the
// config pushed to shill.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
UserPolicyProfileWideVariableExpansions) {
Add8021xWifiService(kServiceWifi1, "UserLevelWifiGuidOrig",
"UserLevelWifiSsid", shill::kStateIdle);
LoginUser(test_account_id_);
const std::string user_hash = user_manager::UserManager::Get()
->FindUser(test_account_id_)
->username_hash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
// Note that while a policy is used here that uses a ClientCertPattern, no
// client certificate is resolved (because no such certificate was imported).
// Still, the "user-specific variable" should be expanded.
SetUserOpenNetworkConfiguration(
user_hash,
OncPolicyToSelectClientCert("{UserLevelWifiGuid}", "UserLevelWifiSsid",
/*issuer_common_name=*/"Example Inc.",
/*identity=*/"${LOGIN_EMAIL}"),
/*wait_applied=*/true);
{
const base::Value::Dict* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(wifi_service_properties);
EXPECT_THAT(*wifi_service_properties,
DictionaryHasValue(shill::kGuidProperty,
base::Value("{UserLevelWifiGuid}")));
// Expect that the EAP.Identity has been replaced
EXPECT_THAT(
*wifi_service_properties,
DictionaryHasValue(shill::kEapIdentityProperty,
base::Value(test_account_id_.GetUserEmail())));
}
}
// Tests that re-applying Ethernet policy retains a manually-set IP address.
// This is a regression test for b/183676832 and b/180365271.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, RetainEthernetIPAddr) {
constexpr char kEthernetGuid[] = "{EthernetGuid}";
shill_service_client_test_->AddService(kServiceEth, "orig_guid_ethernet_any",
"ethernet_any", shill::kTypeEthernet,
shill::kStateOnline, /*visible=*/true);
{
std::string kDeviceONCEverythingRecommended =
base::StringPrintf(R"(
{
"NetworkConfigurations": [
{
"GUID": "%s",
"Name": "EthernetName",
"Type": "Ethernet",
"Ethernet": {
"Authentication": "None"
},
"StaticIPConfig": {
"Recommended": ["Gateway", "IPAddress", "RoutingPrefix",
"NameServers"]
},
"Recommended": ["IPAddressConfigType", "NameServersConfigType"]
}
]
})",
kEthernetGuid);
SetDeviceOpenNetworkConfiguration(kDeviceONCEverythingRecommended,
/*wait_applied=*/true);
}
{
const base::Value::Dict* eth_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceEth);
ASSERT_TRUE(eth_service_properties);
EXPECT_THAT(
*eth_service_properties,
DictionaryHasValue(shill::kGuidProperty, base::Value(kEthernetGuid)));
}
// Check that IP address is modifiable and policy-recommended.
{
auto properties = CrosNetworkConfigGetManagedProperties("{EthernetGuid}");
ASSERT_TRUE(properties);
EXPECT_EQ(properties->ip_address_config_type->policy_source,
network_mojom::PolicySource::kDevicePolicyRecommended);
}
// Simulate setting an IP addr through the UI.
{
auto properties = network_mojom::ConfigProperties::New();
properties->type_config =
network_mojom::NetworkTypeConfigProperties::NewEthernet(
network_mojom::EthernetConfigProperties::New());
properties->ip_address_config_type =
::onc::network_config::kIPConfigTypeStatic;
properties->static_ip_config = network_mojom::IPConfigProperties::New();
properties->static_ip_config->ip_address = "192.168.1.44";
properties->static_ip_config->gateway = "192.168.1.1";
properties->static_ip_config->routing_prefix = 4;
ASSERT_NO_FATAL_FAILURE(
CrosNetworkConfigSetProperties(kEthernetGuid, std::move(properties)));
}
// Verify that the Static IP config has been applied.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceEth);
ASSERT_TRUE(shill_properties);
EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
"192.168.1.44");
}
{
// Modify the policy: Force custom nameserver, but allow IP address to be
// modifiable.
std::string kDeviceONC2 = base::StringPrintf(R"(
{
"NetworkConfigurations": [
{
"GUID": "%s",
"Name": "EthernetName",
"Type": "Ethernet",
"Ethernet": {
"Authentication": "None"
},
"StaticIPConfig": {
"NameServers": ["8.8.3.1", "8.8.2.1"],
"Recommended": ["Gateway", "IPAddress", "RoutingPrefix"]
},
"NameServersConfigType": "Static",
"Recommended": ["IPAddressConfigType"]
}
]
})",
kEthernetGuid);
SetDeviceOpenNetworkConfiguration(kDeviceONC2, /*wait_applied=*/true);
}
// Verify that the Static IP is still active, and the custom name server has
// been applied.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceEth);
ASSERT_TRUE(shill_properties);
EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
"192.168.1.44");
EXPECT_THAT(GetStaticNameServersFromShillProperties(*shill_properties),
ElementsAre("8.8.3.1", "8.8.2.1", "0.0.0.0", "0.0.0.0"));
}
// Modify the policy: Force DHCP ip address
const char kDeviceONC3[] = R"(
{
"NetworkConfigurations": [
{
"GUID": "{EthernetGuid}",
"Name": "EthernetName",
"Type": "Ethernet",
"IPAddressConfigType": "DHCP",
"NameServersConfigType": "DHCP",
"Ethernet": {
"Authentication": "None"
},
"StaticIPConfig": {
"Recommended": []
}
}
]
})";
SetDeviceOpenNetworkConfiguration(kDeviceONC3, /*wait_applied=*/true);
// Check that IP address is not modifiable.
{
auto properties = CrosNetworkConfigGetManagedProperties("{EthernetGuid}");
ASSERT_TRUE(properties);
EXPECT_EQ(properties->ip_address_config_type->policy_source,
network_mojom::PolicySource::kDevicePolicyEnforced);
}
// Verify that the Static IP is gone.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceEth);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(GetStaticIPAddressFromShillProperties(*shill_properties),
IsEmpty());
}
}
// Tests that Ethernet fixes the 'GUID' in 'UIData', if another GUID was
// persisted due to a bug.
// Note: UIData is a String property that chrome fills with a serialized
// dictionary.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest, FixEthernetUIDataGUID) {
constexpr char kEthernetGuid[] = "{EthernetGuid}";
shill_service_client_test_->AddService(kServiceEth, "orig_guid_ethernet_any",
"ethernet_any", shill::kTypeEthernet,
shill::kStateOnline, /*visible=*/true);
// Apply Ethernet policy with a GUID.
std::string kDeviceONC1 = base::StringPrintf(R"(
{
"NetworkConfigurations": [
{
"GUID": "%s",
"Name": "EthernetName",
"Type": "Ethernet",
"Ethernet": {
"Authentication": "None"
}
}
]
})",
kEthernetGuid);
SetDeviceOpenNetworkConfiguration(kDeviceONC1, /*wait_applied=*/true);
// Set GUID in the "user_settings" part of the UIData dictionary to a
// inconsistent value.
{
std::optional<base::Value::Dict> ui_data = GetUIDataDict(kServiceEth);
ASSERT_TRUE(ui_data);
base::Value::Dict* user_settings =
ui_data->EnsureDict(kUIDataKeyUserSettings);
user_settings->Set(::onc::network_config::kGUID, "wrong-guid");
ASSERT_NO_FATAL_FAILURE(SetUIDataDict(kServiceEth, *ui_data));
}
// Verify that UIData now has the incorrect GUID.
{
std::optional<base::Value::Dict> ui_data = GetUIDataDict(kServiceEth);
ASSERT_TRUE(ui_data);
EXPECT_NE(GetGUIDFromUIData(*ui_data), kEthernetGuid);
}
// Re-apply Ethernet policy.
std::string kDeviceONC2 = base::StringPrintf(R"(
{
"NetworkConfigurations": [
{
"GUID": "%s",
"Name": "EthernetName",
"Type": "Ethernet",
"Ethernet": {
"Authentication": "None",
"StaticIPConfig": {
"NameServers": ["8.8.3.1", "8.8.2.1"],
"Recommended": ["Gateway", "IPAddress", "RoutingPrefix"]
},
"NameServersConfigType": "Static",
"Recommended": ["IPAddressConfigType"]
}
}
]
})",
kEthernetGuid);
SetDeviceOpenNetworkConfiguration(kDeviceONC2, /*wait_applied=*/true);
// Check that GUID in the UIData dictionary has been fixed.
{
std::optional<base::Value::Dict> ui_data = GetUIDataDict(kServiceEth);
ASSERT_TRUE(ui_data);
EXPECT_EQ(GetGUIDFromUIData(*ui_data), kEthernetGuid);
}
}
// Tests that a client certificate that has been resolved from a
// ClientCertPattern gets propated to the UI, specifically to the
// CrosNetworkConfig layer.
IN_PROC_BROWSER_TEST_F(
NetworkPolicyApplicationTest,
DevicePolicyClientCertPatternSuccessResultPropagatedToUi) {
const char* kCertKeyFilename = "client_3.pk8";
const char* kCertFilename = "client_3.pem";
const char* kCertIssuerCommonName = "E CA";
const char* kCertSubjectCommonName = "Client Cert F";
ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
kCertFilename, kCertKeyFilename));
Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
"DeviceLevelWifiSsid", shill::kStateOnline);
SetDeviceOpenNetworkConfiguration(
OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
"DeviceLevelWifiSsid",
/*issuer_common_name=*/kCertIssuerCommonName,
/*identity=*/"TestIdentity"),
/*wait_applied=*/true);
{
auto expected_client_cert =
CrosNetworkConfigFindClientCert(kCertSubjectCommonName);
ASSERT_TRUE(expected_client_cert)
<< "Couldn't find cert " << kCertSubjectCommonName;
auto properties =
CrosNetworkConfigGetManagedProperties("{DeviceLevelWifiGuid}");
ASSERT_TRUE(properties);
ASSERT_EQ(properties->type_properties->which(),
network_mojom::NetworkTypeManagedProperties::Tag::kWifi);
const auto& eap = properties->type_properties->get_wifi()->eap;
// Check the selected certificate
ASSERT_TRUE(eap->client_cert_pkcs11_id);
EXPECT_EQ(eap->client_cert_pkcs11_id->active_value,
expected_client_cert->pem_or_id);
EXPECT_EQ(eap->client_cert_pkcs11_id->policy_source,
network_mojom::PolicySource::kDevicePolicyEnforced);
ASSERT_EQ(eap->client_cert_pkcs11_id->policy_value,
std::make_optional(expected_client_cert->pem_or_id));
// The type should be "PKCS11Id" in the UI.
ASSERT_TRUE(eap->client_cert_type);
EXPECT_EQ(eap->client_cert_type->active_value, onc::client_cert::kPKCS11Id);
EXPECT_EQ(eap->client_cert_type->policy_source,
network_mojom::PolicySource::kDevicePolicyEnforced);
EXPECT_EQ(eap->client_cert_type->policy_value, onc::client_cert::kPKCS11Id);
}
}
// Tests that resolution of a ClientCertPattern is tirggered when a client
// certificate is imported.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
DevicePolicyClientCertResolutionTriggeredOnCertImport) {
const char* kCertKeyFilename = "client_3.pk8";
const char* kCertFilename = "client_3.pem";
const char* kCertIssuerCommonName = "E CA";
const char* kCertSubjectCommonName = "Client Cert F";
Add8021xWifiService(kServiceWifi1, "DeviceLevelWifiGuidOrig",
"DeviceLevelWifiSsid", shill::kStateOnline);
SetDeviceOpenNetworkConfiguration(
OncPolicyToSelectClientCert("{DeviceLevelWifiGuid}",
"DeviceLevelWifiSsid",
/*issuer_common_name=*/kCertIssuerCommonName,
/*identity=*/"TestIdentity"),
/*wait_applied=*/true);
{
// The ClientCertPattern could not be resolved yet, the UI (the
// CrosNetworkConfig layer) gets an empty PKCS11Id.
auto properties =
CrosNetworkConfigGetManagedProperties("{DeviceLevelWifiGuid}");
ASSERT_TRUE(properties);
ASSERT_EQ(properties->type_properties->which(),
network_mojom::NetworkTypeManagedProperties::Tag::kWifi);
const auto& eap = properties->type_properties->get_wifi()->eap;
// Check that the selected certificate field is empty.
ASSERT_TRUE(eap->client_cert_pkcs11_id);
EXPECT_EQ(eap->client_cert_pkcs11_id->active_value, std::string());
EXPECT_EQ(eap->client_cert_pkcs11_id->policy_source,
network_mojom::PolicySource::kDevicePolicyEnforced);
EXPECT_EQ(eap->client_cert_pkcs11_id->policy_value, std::string());
// The type should still be "PKCS11Id" for the UI layer to indicate an empty
// selection.
ASSERT_TRUE(eap->client_cert_type);
EXPECT_EQ(eap->client_cert_type->active_value, onc::client_cert::kPKCS11Id);
EXPECT_EQ(eap->client_cert_type->policy_source,
network_mojom::PolicySource::kDevicePolicyEnforced);
EXPECT_EQ(eap->client_cert_type->policy_value, onc::client_cert::kPKCS11Id);
}
ServicePropertyValueWatcher eap_cert_id_watcher(
shill_service_client_test_, kServiceWifi1, shill::kEapCertIdProperty);
ASSERT_NO_FATAL_FAILURE(ImportCert(net::GetTestCertsDirectory(),
kCertFilename, kCertKeyFilename));
eap_cert_id_watcher.WaitForNonEmptyValue();
{
auto expected_client_cert =
CrosNetworkConfigFindClientCert(kCertSubjectCommonName);
ASSERT_TRUE(expected_client_cert)
<< "Couldn't find cert " << kCertSubjectCommonName;
auto properties =
CrosNetworkConfigGetManagedProperties("{DeviceLevelWifiGuid}");
ASSERT_TRUE(properties);
ASSERT_EQ(properties->type_properties->which(),
network_mojom::NetworkTypeManagedProperties::Tag::kWifi);
const auto& eap = properties->type_properties->get_wifi()->eap;
// Check the selected certificate
ASSERT_TRUE(eap->client_cert_pkcs11_id);
EXPECT_EQ(eap->client_cert_pkcs11_id->active_value,
expected_client_cert->pem_or_id);
EXPECT_EQ(eap->client_cert_pkcs11_id->policy_source,
network_mojom::PolicySource::kDevicePolicyEnforced);
ASSERT_EQ(eap->client_cert_pkcs11_id->policy_value,
std::make_optional(expected_client_cert->pem_or_id));
// The type should be "PKCS11Id" in the UI.
ASSERT_TRUE(eap->client_cert_type);
EXPECT_EQ(eap->client_cert_type->active_value, onc::client_cert::kPKCS11Id);
EXPECT_EQ(eap->client_cert_type->policy_source,
network_mojom::PolicySource::kDevicePolicyEnforced);
EXPECT_EQ(eap->client_cert_type->policy_value, onc::client_cert::kPKCS11Id);
}
}
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
RetainEthernetIPAddrUnmanagedToManaged) {
constexpr char kEthernetGuidUnmanaged[] = "{orig_guid_ethernet_any}";
constexpr char kEthernetGuidManaged[] = "{EthernetGuid}";
constexpr char kStaticIpAddr[] = "192.168.1.44";
CrosNetworkConfigGuidsAvailableWaiter available_waiter(
cros_network_config_.get(), {kEthernetGuidUnmanaged});
shill_service_client_test_->AddService(kServiceEth, kEthernetGuidUnmanaged,
"ethernet_any", shill::kTypeEthernet,
shill::kStateOnline, /*visible=*/true);
shill_profile_client_test_->AddService(kSharedProfilePath, kServiceEth);
available_waiter.Wait();
// Check that IP address is modifiable and does not come from policy
{
auto properties =
CrosNetworkConfigGetManagedProperties(kEthernetGuidUnmanaged);
ASSERT_TRUE(properties);
EXPECT_EQ(properties->ip_address_config_type->policy_source,
network_mojom::PolicySource::kNone);
}
// Simulate setting an IP address through the UI.
{
auto properties = network_mojom::ConfigProperties::New();
properties->type_config =
network_mojom::NetworkTypeConfigProperties::NewEthernet(
network_mojom::EthernetConfigProperties::New());
properties->ip_address_config_type =
::onc::network_config::kIPConfigTypeStatic;
properties->static_ip_config = network_mojom::IPConfigProperties::New();
properties->static_ip_config->ip_address = kStaticIpAddr;
properties->static_ip_config->gateway = "192.168.1.1";
properties->static_ip_config->routing_prefix = 4;
ASSERT_NO_FATAL_FAILURE(CrosNetworkConfigSetProperties(
kEthernetGuidUnmanaged, std::move(properties)));
}
// Verify that the Static IP config has been applied.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceEth);
ASSERT_TRUE(shill_properties);
EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
kStaticIpAddr);
}
// Apply policy: Explicitly recommend both IP address and Nameservers,
// allowing the user to modify them.
const std::string kDeviceONCEverythingRecommended =
base::StringPrintf(R"(
{
"NetworkConfigurations": [
{
"GUID": "%s",
"Name": "EthernetName",
"Type": "Ethernet",
"Ethernet": {
"Authentication": "None"
},
"StaticIPConfig": {
"Recommended": ["Gateway", "IPAddress", "RoutingPrefix",
"NameServers"]
},
"Recommended": ["IPAddressConfigType", "NameServersConfigType"]
}
]
})",
kEthernetGuidManaged);
SetDeviceOpenNetworkConfiguration(kDeviceONCEverythingRecommended,
/*wait_applied=*/true);
// Verify that the Static IP is still active.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceEth);
ASSERT_TRUE(shill_properties);
EXPECT_EQ(GetStaticIPAddressFromShillProperties(*shill_properties),
kStaticIpAddr);
}
// Check that IP address is modifiable and "Recommended" by policy
{
auto properties =
CrosNetworkConfigGetManagedProperties(kEthernetGuidManaged);
ASSERT_TRUE(properties);
EXPECT_EQ(properties->ip_address_config_type->policy_source,
network_mojom::PolicySource::kDevicePolicyRecommended);
}
}
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
EphemeralActions_NotActive) {
constexpr char kGuidWifi1[] = "guid_wifi_1";
AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
kUserIdentity);
AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
shill::kStateIdle);
shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);
const std::string kDeviceONC = base::StringPrintf(
R"(
{
"GlobalNetworkConfiguration": {
},
"NetworkConfigurations": [
{
"GUID": "%s",
"Type": "WiFi",
"Name": "Managed wifi1",
"WiFi": {
"HexSSID": "7769666931", // "wifi1"
"SSID": "wifi1",
"Security": "WPA-EAP",
"EAP": {
"Outer": "PEAP",
"Inner": "MSCHAPv2",
"SaveCredentials": true,
"Recommended": ["Identity", "Password"]
}
}
}
],
"Type": "UnencryptedConfiguration"
})",
kGuidWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC,
/*wait_applied=*/true);
// Verify that the recommended EAP.Identity of the managed wifi service has
// not been wiped.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(*shill_properties,
DictionaryHasValue(shill::kEapIdentityProperty,
base::Value(kUserIdentity)));
}
// Verify that the unmanaged wifi service has not been wiped.
EXPECT_TRUE(shill_profile_client_test_->HasService(kServiceWifi2));
}
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationEphemeralActionsEnabledTest,
EphemeralActions_Active) {
constexpr char kGuidWifi1[] = "guid_wifi_1";
AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
kUserIdentity);
AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
shill::kStateIdle);
shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);
const std::string kDeviceONC = base::StringPrintf(
R"(
{
"GlobalNetworkConfiguration": {
"RecommendedValuesAreEphemeral": true,
"UserCreatedNetworkConfigurationsAreEphemeral": true
},
"NetworkConfigurations": [
{
"GUID": "%s",
"Type": "WiFi",
"Name": "Managed wifi1",
"WiFi": {
"HexSSID": "7769666931", // "wifi1"
"SSID": "wifi1",
"Security": "WPA-EAP",
"EAP": {
"Outer": "PEAP",
"Inner": "MSCHAPv2",
"SaveCredentials": true,
"Recommended": ["Identity", "Password"]
}
}
}
],
"Type": "UnencryptedConfiguration"
})",
kGuidWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC,
/*wait_applied=*/true);
// Verify that the recommended EAP.Identity of the managed wifi service has
// been wiped.
std::optional<std::string> new_service_path =
shill_service_client_test_->FindServiceMatchingGUID(kGuidWifi1);
ASSERT_TRUE(new_service_path);
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(*new_service_path);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(shill_properties->FindString(shill::kEapIdentityProperty),
IsNull());
// Also check some other property to see that it has not been wiped.
EXPECT_THAT(shill_properties->FindString(shill::kEapPhase2AuthProperty),
Pointee(Eq("auth=MSCHAPV2")));
}
// Verify that the unmanaged wifi service has been wiped.
EXPECT_FALSE(shill_profile_client_test_->HasService(kServiceWifi2));
// Set EAP.Identity back to a non-empty value.
shill_service_client_test_->SetServiceProperty(*new_service_path,
shill::kEapIdentityProperty,
base::Value(kUserIdentity));
// Simulate that the device goes to sleep and wakes up, and expect that it
// leads to this entry getting deleted and then re-created without
// "EAP.Identity" again. Internally this process is implemented as a policy
// application, so we can use that to detect the end of the process.
ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
chromeos::FakePowerManagerClient::Get()->SendSuspendDone(base::Minutes(10));
network_policy_application_observer.WaitPoliciesApplied(
/*userhash=*/std::string());
// Verify that the recommended EAP.Identity of the recreated managed wifi
// service has been wiped again.
new_service_path =
shill_service_client_test_->FindServiceMatchingGUID(kGuidWifi1);
ASSERT_TRUE(new_service_path);
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(*new_service_path);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(shill_properties->FindString(shill::kEapIdentityProperty),
IsNull());
}
// Simulate user log-in
LoginUser(test_account_id_);
// Set EAP.Identity back to a non-empty value.
shill_service_client_test_->SetServiceProperty(*new_service_path,
shill::kEapIdentityProperty,
base::Value(kUserIdentity));
// Simulate that the device goes to sleep and wakes up.
chromeos::FakePowerManagerClient::Get()->SendSuspendDone(base::Minutes(10));
base::RunLoop().RunUntilIdle();
// Verify that the recommended EAP.Identity of the managed wifi service has
// not been wiped because the "ephemeral actions" don't apply within active
// sessions.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(*new_service_path);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(*shill_properties,
DictionaryHasValue(shill::kEapIdentityProperty,
base::Value("user_identity")));
}
}
IN_PROC_BROWSER_TEST_F(
NetworkPolicyApplicationEphemeralActionsEnabledUnenrolledTest,
EphemeralActions_NoWipeOnEnterpriseEnrollment) {
constexpr char kGuidWifi1[] = "guid_wifi_1";
AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
kUserIdentity);
AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
shill::kStateIdle);
shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);
// The device changes state to enterprise-enrolled here, simulating enterprise
// enrollment with incoming device policy.
MarkEnterpriseEnrolled();
const std::string kDeviceONC = base::StringPrintf(
R"(
{
"GlobalNetworkConfiguration": {
"RecommendedValuesAreEphemeral": true,
"UserCreatedNetworkConfigurationsAreEphemeral": true
},
"NetworkConfigurations": [
{
"GUID": "%s",
"Type": "WiFi",
"Name": "Managed wifi1",
"WiFi": {
"HexSSID": "7769666931", // "wifi1"
"SSID": "wifi1",
"Security": "WPA-EAP",
"EAP": {
"Outer": "PEAP",
"Inner": "MSCHAPv2",
"SaveCredentials": true,
"Recommended": ["Identity", "Password"]
}
}
}
],
"Type": "UnencryptedConfiguration"
})",
kGuidWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC,
/*wait_applied=*/true);
// Verify that the unmanaged wifi service has not been wiped.
EXPECT_TRUE(shill_profile_client_test_->HasService(kServiceWifi2));
// Simulate that the device goes to sleep and wakes up, and check that the
// unmanaged wifi gets wiped then.
ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
chromeos::FakePowerManagerClient::Get()->SendSuspendDone(base::Minutes(10));
network_policy_application_observer.WaitPoliciesApplied(
/*userhash=*/std::string());
EXPECT_FALSE(shill_profile_client_test_->HasService(kServiceWifi2));
}
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationEphemeralActionsDisabledTest,
EphemeralActions_ActiveByPolicy) {
constexpr char kGuidWifi1[] = "guid_wifi_1";
AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
kUserIdentity);
AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
shill::kStateIdle);
shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);
SetDeviceEphemeralNetworkPoliciesEnabledPolicy(true);
const std::string kDeviceONC = base::StringPrintf(
R"(
{
"GlobalNetworkConfiguration": {
"RecommendedValuesAreEphemeral": true,
"UserCreatedNetworkConfigurationsAreEphemeral": true
},
"NetworkConfigurations": [
{
"GUID": "%s",
"Type": "WiFi",
"Name": "Managed wifi1",
"WiFi": {
"HexSSID": "7769666931", // "wifi1"
"SSID": "wifi1",
"Security": "WPA-EAP",
"EAP": {
"Outer": "PEAP",
"Inner": "MSCHAPv2",
"SaveCredentials": true,
"Recommended": ["Identity", "Password"]
}
}
}
],
"Type": "UnencryptedConfiguration"
})",
kGuidWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC,
/*wait_applied=*/true);
// Verify that the recommended EAP.Identity of the managed wifi service has
// been wiped.
{
std::optional<std::string> new_service_path =
shill_service_client_test_->FindServiceMatchingGUID(kGuidWifi1);
ASSERT_TRUE(new_service_path);
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(*new_service_path);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(shill_properties->FindString(shill::kEapIdentityProperty),
IsNull());
// Also check some other property to see that it has not been wiped.
EXPECT_THAT(shill_properties->FindString(shill::kEapPhase2AuthProperty),
Pointee(Eq("auth=MSCHAPV2")));
}
// Verify that the unmanaged wifi service has been wiped.
EXPECT_FALSE(shill_profile_client_test_->HasService(kServiceWifi2));
}
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationEphemeralActionsDisabledTest,
EphemeralActions_Active) {
constexpr char kGuidWifi1[] = "guid_wifi_1";
AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
kUserIdentity);
AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
shill::kStateIdle);
shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);
const std::string kDeviceONC = base::StringPrintf(
R"(
{
"GlobalNetworkConfiguration": {
"RecommendedValuesAreEphemeral": true,
"UserCreatedNetworkConfigurationsAreEphemeral": true
},
"NetworkConfigurations": [
{
"GUID": "%s",
"Type": "WiFi",
"Name": "Managed wifi1",
"WiFi": {
"HexSSID": "7769666931", // "wifi1"
"SSID": "wifi1",
"Security": "WPA-EAP",
"EAP": {
"Outer": "PEAP",
"Inner": "MSCHAPv2",
"SaveCredentials": true,
"Recommended": ["Identity", "Password"]
}
}
}
],
"Type": "UnencryptedConfiguration"
})",
kGuidWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC,
/*wait_applied=*/true);
// Verify that the recommended EAP.Identity of the managed wifi service has
// not been wiped.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(*shill_properties,
DictionaryHasValue(shill::kEapIdentityProperty,
base::Value(kUserIdentity)));
}
// Verify that the unmanaged wifi service has not been wiped.
EXPECT_TRUE(shill_profile_client_test_->HasService(kServiceWifi2));
}
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationEphemeralActionsKillSwitchTest,
EphemeralActions_ActiveByPolicy) {
constexpr char kGuidWifi1[] = "guid_wifi_1";
AddSharedDevicePolicyMschapv2Service(kServiceWifi1, kGuidWifi1, "wifi1",
kUserIdentity);
AddPskWifiService(kServiceWifi2, "unmanaged_wifi2_guid", "UnmanagedWifi2",
shill::kStateIdle);
shill_profile_client_test_->AddService(kSharedProfilePath, kServiceWifi2);
SetDeviceEphemeralNetworkPoliciesEnabledPolicy(true);
const std::string kDeviceONC = base::StringPrintf(
R"(
{
"GlobalNetworkConfiguration": {
"RecommendedValuesAreEphemeral": true,
"UserCreatedNetworkConfigurationsAreEphemeral": true
},
"NetworkConfigurations": [
{
"GUID": "%s",
"Type": "WiFi",
"Name": "Managed wifi1",
"WiFi": {
"HexSSID": "7769666931", // "wifi1"
"SSID": "wifi1",
"Security": "WPA-EAP",
"EAP": {
"Outer": "PEAP",
"Inner": "MSCHAPv2",
"SaveCredentials": true,
"Recommended": ["Identity", "Password"]
}
}
}
],
"Type": "UnencryptedConfiguration"
})",
kGuidWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC,
/*wait_applied=*/true);
// Verify that the recommended EAP.Identity of the managed wifi service has
// not been wiped.
{
const base::Value::Dict* shill_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(shill_properties);
EXPECT_THAT(*shill_properties,
DictionaryHasValue(shill::kEapIdentityProperty,
base::Value(kUserIdentity)));
}
// Verify that the unmanaged wifi service has not been wiped.
EXPECT_TRUE(shill_profile_client_test_->HasService(kServiceWifi2));
}
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
CheckCaptivePortal_AllValues) {
constexpr char kGuidWifiTrue[] = "guid_wifi_true";
constexpr char kGuidWifiFalse[] = "guid_wifi_false";
constexpr char kGuidWifiHTTPOnly[] = "guid_wifi_http_only";
constexpr char kNetworkNameTrue[] = "NetworkTrue";
constexpr char kNetworkNameFalse[] = "NetworkFalse";
constexpr char kNetworkNameHTTPOnly[] = "NetworkHTTPOnly";
constexpr char kWifiNameTrue[] = "WifiTrue";
constexpr char kWifiNameFalse[] = "WifiFalse";
constexpr char kWifiNameHTTPOnly[] = "WifiHTTPOnly";
AddPskWifiService(kServiceWifi1, kGuidWifiTrue, kWifiNameTrue,
shill::kStateIdle);
AddPskWifiService(kServiceWifi2, kGuidWifiFalse, kWifiNameFalse,
shill::kStateIdle);
AddPskWifiService(kServiceWifi3, kGuidWifiHTTPOnly, kWifiNameHTTPOnly,
shill::kStateIdle);
const char* kConfig = R"(
{
"GlobalNetworkConfiguration": {
},
"NetworkConfigurations": [
{
"GUID": "%s",
"Name": "%s",
"Type": "WiFi",
"CheckCaptivePortal": "%s",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "%s",
"Security": "WPA-PSK"
}
},
{
"GUID": "%s",
"Name": "%s",
"Type": "WiFi",
"CheckCaptivePortal": "%s",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "%s",
"Security": "WPA-PSK"
}
},
{
"GUID": "%s",
"Name": "%s",
"Type": "WiFi",
"CheckCaptivePortal": "%s",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"Passphrase": "DeviceLevelWifiPwd",
"SSID": "%s",
"Security": "WPA-PSK"
}
}
],
"Type": "UnencryptedConfiguration"
})";
const std::string kDeviceONC = base::StringPrintf(
kConfig, kGuidWifiTrue, kNetworkNameTrue,
::onc::check_captive_portal::kTrue, kWifiNameTrue, kGuidWifiFalse,
kNetworkNameFalse, ::onc::check_captive_portal::kFalse, kWifiNameFalse,
kGuidWifiHTTPOnly, kNetworkNameHTTPOnly,
::onc::check_captive_portal::kHTTPOnly, kWifiNameHTTPOnly);
SetDeviceOpenNetworkConfiguration(kDeviceONC,
/*wait_applied=*/true);
// Verify that the CheckCaptivePortal of the managed Wi-Fi services are set
// correctly.
{
const base::Value::Dict* shill_properties1 =
shill_service_client_test_->GetServiceProperties(kServiceWifi1);
ASSERT_TRUE(shill_properties1);
EXPECT_THAT(
*shill_properties1,
DictionaryHasValue(shill::kCheckPortalProperty, base::Value("true")));
const base::Value::Dict* shill_properties2 =
shill_service_client_test_->GetServiceProperties(kServiceWifi2);
ASSERT_TRUE(shill_properties2);
EXPECT_THAT(
*shill_properties2,
DictionaryHasValue(shill::kCheckPortalProperty, base::Value("false")));
const base::Value::Dict* shill_properties3 =
shill_service_client_test_->GetServiceProperties(kServiceWifi3);
ASSERT_TRUE(shill_properties3);
EXPECT_THAT(*shill_properties3,
DictionaryHasValue(shill::kCheckPortalProperty,
base::Value("http-only")));
}
}
// Tests that when
// - a policy-provided network has a ClientCertPattern which contains an
// EnrollmentURI and
// - the ClientCertPattern doesn't match any installed client certificate,
// then, on a connection attempt through the system tray, a dialog is triggered
// which suggests to enroll a client certificate (the somewhat confusingly named
// "enrollment dialog" - it's not related to enterprise enrollment).
// Also tests that when accepting that dialog, a browser tab is opened,
// navigating to the provided EnrollmentURI.
//
// This is a regression test for b/319188170.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
ClientCertEnrollmentUriTriggered) {
Add8021xWifiService(kServiceWifi1, "UserLevelWifiGuidOrig",
"UserLevelWifiSsid", shill::kStateIdle);
LoginUser(test_account_id_);
const std::string user_hash = user_manager::UserManager::Get()
->FindUser(test_account_id_)
->username_hash();
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
// Set a policy that uses a ClientCertPattern which has an EnrollmentURI and
// will not resolve to any client certificate (no client certificate has been
// installed/imported at all).
const char kUserONC[] = R"(
{
"NetworkConfigurations": [
{
"GUID": "{user-policy-for-wifi}",
"Name": "OncPolicyToSelectClientCert",
"Type": "WiFi",
"WiFi": {
"EAP": {
"Outer": "EAP-TLS",
"ClientCertType": "Pattern",
"Identity": "SomeIdentity",
"ClientCertPattern": {
"Issuer": {
"CommonName": "DoesntMatchAnything"
},
"EnrollmentURI": ["chrome://version"]
}
},
"SSID": "UserLevelWifiSsid",
"Security": "WPA-EAP"
}
}
]
})";
SetUserOpenNetworkConfiguration(user_hash, kUserONC,
/*wait_applied=*/true);
// Click on the SSID in the system tray and expect the (client certificate)
// enrollment dialog to appear.
views::NamedWidgetShownWaiter dialog_widget_waiter(
views::test::AnyWidgetTestPasskey(), ash::enrollment::kWidgetName);
ASSERT_NO_FATAL_FAILURE(ConnectToSsidUsingSystemTray("UserLevelWifiSsid"));
views::Widget* dialog_widget = dialog_widget_waiter.WaitIfNeededAndGet();
ASSERT_TRUE(dialog_widget);
// Accept the enrollment dialog and expect a corresponding tab with the
// EnrollmentURI to be opened.
ui_test_utils::AllBrowserTabAddedWaiter tab_waiter;
ASSERT_NO_FATAL_FAILURE(AcceptCertEnrollmentDialog(dialog_widget));
content::WebContents* const tab_contents = tab_waiter.Wait();
ASSERT_TRUE(tab_contents);
EXPECT_EQ(tab_contents->GetVisibleURL(), GURL("chrome://version"));
}
} // namespace policy