blob: 7637c987e43e33bcb0fc4709b8d6a0e04ec324ce [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <sys/types.h>
#include <utility>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/test/values_test_util.h"
#include "chrome/browser/chromeos/login/login_manager_test.h"
#include "chrome/browser/chromeos/login/test/login_manager_mixin.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chromeos/constants/chromeos_switches.h"
#include "chromeos/dbus/shill/shill_device_client.h"
#include "chromeos/dbus/shill/shill_manager_client.h"
#include "chromeos/dbus/shill/shill_profile_client.h"
#include "chromeos/dbus/shill/shill_service_client.h"
#include "chromeos/network/managed_network_configuration_handler.h"
#include "chromeos/network/network_handler.h"
#include "chromeos/network/network_policy_observer.h"
#include "components/account_id/account_id.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/cloud/policy_builder.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 "content/public/test/browser_test.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
using base::test::DictionaryHasValue;
namespace chromeos {
namespace {
constexpr char kUserProfilePath[] = "user_profile";
constexpr char kSharedProfilePath[] = "/profile/default";
constexpr char kServiceWifi1[] = "/service/1";
constexpr char kServiceWifi2[] = "/service/2";
// A utility to wait until a FakeShillServiceClient's service has been
// connected.
// Usage:
// (1) Construct a ServiceConnectedWaiter, specifying the shill service path
// that is expected to connect.
// (2) Call ServiceConnectedWaiter::Wait
// Wait will return when the service passed to (1) connects. If the service has
// connected between (1) and (2), Wait returns immediately. Note that this class
// does not evaluate whether the service was connected before (1).
class ServiceConnectedWaiter {
public:
ServiceConnectedWaiter(
ShillServiceClient::TestInterface* shill_service_client_test,
const std::string& service_path)
: shill_service_client_test_(shill_service_client_test),
service_path_(service_path) {
shill_service_client_test_->SetConnectBehavior(service_path_,
run_loop_.QuitClosure());
}
// Waits until the |service_path| passed to the constructor has connected.
// If it has connected since the constructor has run, will return immediately.
void Wait() {
run_loop_.Run();
shill_service_client_test_->SetConnectBehavior(service_path_,
base::RepeatingClosure());
}
private:
ShillServiceClient::TestInterface* shill_service_client_test_;
std::string service_path_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(ServiceConnectedWaiter);
};
// Registers itself as NetworkPolicyObserver and records events for
// NetworkPolicyObserver::PoliciesApplied and
// NetworkPolicyObserver::PolicyAppliedtoNetwork.
class ScopedNetworkPolicyApplicationObserver : public NetworkPolicyObserver {
public:
ScopedNetworkPolicyApplicationObserver() {
ManagedNetworkConfigurationHandler* managed_network_configuration_handler =
NetworkHandler::Get()->managed_network_configuration_handler();
managed_network_configuration_handler->AddObserver(this);
}
~ScopedNetworkPolicyApplicationObserver() override {
ManagedNetworkConfigurationHandler* managed_network_configuration_handler =
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);
}
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();
}
private:
std::vector<std::string> policies_applied_events_;
std::vector<std::string> policy_applied_to_network_events_;
};
} // namespace
// This class is used for implementing integration tests for network policy
// application across sign-in screen and/or user session.
class NetworkPolicyApplicationTest : public LoginManagerTest {
public:
NetworkPolicyApplicationTest() : LoginManagerTest() {
login_mixin_.AppendRegularUsers(1);
test_account_id_ = login_mixin_.users()[0].account_id;
}
protected:
// InProcessBrowserTest:
void SetUp() override {
EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
.WillRepeatedly(testing::Return(true));
policy::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(switches::kAllowFailedPolicyFetchForTest);
}
void SetUpInProcessBrowserTestFixture() override {
LoginManagerTest::SetUpInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
LoginManagerTest::SetUpOnMainThread();
shill_manager_client_test_ = ShillManagerClient::Get()->GetTestInterface();
shill_service_client_test_ = ShillServiceClient::Get()->GetTestInterface();
shill_profile_client_test_ = ShillProfileClient::Get()->GetTestInterface();
shill_device_client_test_ = ShillDeviceClient::Get()->GetTestInterface();
shill_service_client_test_->ClearServices();
shill_device_client_test_->ClearDevices();
shill_profile_client_test_->ClearProfiles();
shill_manager_client_test_->AddTechnology(shill::kTypeWifi,
true /* enabled */);
shill_device_client_test_->AddDevice("/device/wifi1", shill::kTypeWifi,
"stub_wifi_device1");
shill_profile_client_test_->AddProfile(kSharedProfilePath, "");
shill_service_client_test_->ClearServices();
}
void SetDeviceOpenNetworkConfiguration(
const std::string& device_onc_policy_blob) {
current_policy_.Set(
policy::key::kDeviceOpenNetworkConfiguration,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_CLOUD,
std::make_unique<base::Value>(device_onc_policy_blob), nullptr);
policy_provider_.UpdateChromePolicy(current_policy_);
}
void SetUserOpenNetworkConfiguration(
const std::string& user_onc_policy_blob) {
current_policy_.Set(
policy::key::kOpenNetworkConfiguration, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
std::make_unique<base::Value>(user_onc_policy_blob), nullptr);
policy_provider_.UpdateChromePolicy(current_policy_);
}
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;
ShillServiceClient::Get()->Connect(
dbus::ObjectPath(service_path), run_loop.QuitClosure(),
base::BindOnce(&NetworkPolicyApplicationTest::OnConnectToServiceFailed,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
}
// Unowned pointers -- just pointers to the singleton instances.
ShillManagerClient::TestInterface* shill_manager_client_test_ = nullptr;
ShillServiceClient::TestInterface* shill_service_client_test_ = nullptr;
ShillProfileClient::TestInterface* shill_profile_client_test_ = nullptr;
ShillDeviceClient::TestInterface* shill_device_client_test_ = nullptr;
LoginManagerMixin login_mixin_{&mixin_host_};
AccountId test_account_id_;
private:
policy::MockConfigurationPolicyProvider policy_provider_;
policy::PolicyMap current_policy_;
DISALLOW_COPY_AND_ASSIGN(NetworkPolicyApplicationTest);
};
// 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.
IN_PROC_BROWSER_TEST_F(NetworkPolicyApplicationTest,
OnlyPolicyAutoconnectWithSlowUserPolicyApplication) {
ScopedNetworkPolicyApplicationObserver network_policy_application_observer;
// Set up two services.
shill_service_client_test_->AddService(
kServiceWifi1, "wifi_orig_guid_1", "WifiOne", shill::kTypeWifi,
shill::kStateOnline, true /* add_to_visible */);
shill_service_client_test_->SetServiceProperty(
kServiceWifi1, shill::kSSIDProperty, base::Value("WifiOne"));
shill_service_client_test_->SetServiceProperty(
kServiceWifi1, shill::kSecurityClassProperty,
base::Value(shill::kSecurityPsk));
shill_service_client_test_->AddService(
kServiceWifi2, "wifi_orig_guid_2", "WifiTwo", shill::kTypeWifi,
shill::kStateOnline, true /* add_to_visible */);
shill_service_client_test_->SetServiceProperty(
kServiceWifi2, shill::kSSIDProperty, base::Value("WifiTwo"));
shill_service_client_test_->SetServiceProperty(
kServiceWifi2, shill::kSecurityClassProperty,
base::Value(shill::kSecurityPsk));
// 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"
}
}
]
})";
ServiceConnectedWaiter wifi_one_connected_waiter(shill_service_client_test_,
kServiceWifi1);
shill_manager_client_test_->SetBestServiceToConnect(kServiceWifi1);
SetDeviceOpenNetworkConfiguration(kDeviceONC);
wifi_one_connected_waiter.Wait();
EXPECT_THAT(
network_policy_application_observer.policy_applied_to_network_events(),
testing::ElementsAre(kServiceWifi1));
EXPECT_THAT(network_policy_application_observer.policies_applied_events(),
testing::ElementsAre(std::string() /* shill shared profile */));
network_policy_application_observer.ResetEvents();
base::Optional<std::string> wifi_service =
shill_service_client_test_->FindServiceMatchingGUID(
"{device-policy-for-Wifi1}");
ASSERT_TRUE(wifi_service);
{
const base::Value* 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);
std::string user_hash =
chromeos::ProfileHelper::GetUserIdHashByUserIdForTesting(
test_account_id_.GetUserEmail());
LoginUser(test_account_id_);
shill_profile_client_test_->AddProfile(kUserProfilePath, user_hash);
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(kUserONC);
base::RunLoop().RunUntilIdle();
// 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());
shill_service_client_test_->SetHoldBackServicePropertyUpdates(false);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(
network_policy_application_observer.policy_applied_to_network_events(),
testing::ElementsAre(kServiceWifi1, kServiceWifi2));
EXPECT_THAT(network_policy_application_observer.policies_applied_events(),
testing::ElementsAre(user_hash));
// Expect that the same service path now has the user policy GUID.
{
const base::Value* 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)));
}
base::Optional<std::string> wifi2_service =
shill_service_client_test_->FindServiceMatchingGUID(
"{user-policy-for-Wifi2}");
ASSERT_TRUE(wifi2_service);
{
const base::Value* 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)));
}
}
// 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.
shill_service_client_test_->AddService(
kServiceWifi1, "wifi_orig_guid_1", "WifiOne", shill::kTypeWifi,
shill::kStateOnline, true /* add_to_visible */);
shill_service_client_test_->SetServiceProperty(
kServiceWifi1, shill::kSSIDProperty, base::Value("WifiOne"));
shill_service_client_test_->SetServiceProperty(
kServiceWifi1, shill::kSecurityClassProperty,
base::Value(shill::kSecurityPsk));
shill_service_client_test_->AddService(
kServiceWifi2, "wifi_orig_guid_2", "WifiTwo", shill::kTypeWifi,
shill::kStateOnline, true /* add_to_visible */);
shill_service_client_test_->SetServiceProperty(
kServiceWifi2, shill::kSSIDProperty, base::Value("WifiTwo"));
shill_service_client_test_->SetServiceProperty(
kServiceWifi2, shill::kSecurityClassProperty,
base::Value(shill::kSecurityPsk));
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);
{
const base::Value* 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);
{
const base::Value* wifi_service_properties =
shill_service_client_test_->GetServiceProperties(kServiceWifi2);
ASSERT_TRUE(wifi_service_properties);
EXPECT_FALSE(wifi_service_properties->FindKey(shill::kGuidProperty));
}
{
const base::Value* 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}")));
}
}
} // namespace chromeos