#include "chromeos/ash/components/network/auto_connect_handler.h"
#include "ash/constants/ash_features.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/task/single_thread_task_runner.h"
#include "base/values.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/device_state.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler.h"
#include "chromeos/ash/components/network/managed_state.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "dbus/object_path.h"
#include "network_connection_observer.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace ash {
namespace {
constexpr char kESimPolicyDisconnectByPolicyHistogram[] =
"Network.Cellular.ESim.DisconnectByPolicy.Result";
constexpr char kPSimPolicyDisconnectByPolicyHistogram[] =
"Network.Cellular.PSim.DisconnectByPolicy.Result";
void RecordDisconnectByPolicyResult(const NetworkState* network, bool success) {
if (network->type() == shill::kTypeCellular) {
if (network->eid().empty()) {
base::UmaHistogramBoolean(kPSimPolicyDisconnectByPolicyHistogram,
success);
return;
}
base::UmaHistogramBoolean(kESimPolicyDisconnectByPolicyHistogram, success);
}
}
void DisconnectErrorCallback(const NetworkState* network,
const std::string& error_name) {
RecordDisconnectByPolicyResult(network, false);
NET_LOG(ERROR) << "AutoConnectHandler.Disconnect failed for: "
<< NetworkPathId(network->path())
<< " Error name: " << error_name;
}
void RemoveNetworkConfigurationErrorCallback(const std::string& error_name) {
NET_LOG(ERROR) << "AutoConnectHandler RemoveNetworkConfiguration failed."
<< " Error name: " << error_name;
}
void ConnectToNetworkErrorCallback(const std::string& error_name) {
NET_LOG(ERROR) << "AutoConnectHandler ConnectToNetwork failed."
<< " Error name: " << error_name;
}
void SetPropertiesErrorCallback(const std::string& error_name) {
NET_LOG(ERROR) << "AutoConnectHandler SetProperties failed."
<< " Error name: " << error_name;
}
bool HasTypeWifi(const ManagedState* shill_object) {
return NetworkTypePattern::WiFi().MatchesType(shill_object->type());
}
std::string AutoConnectReasonsToString(int auto_connect_reasons) {
std::string result;
if (auto_connect_reasons &
AutoConnectHandler::AutoConnectReason::AUTO_CONNECT_REASON_LOGGED_IN) {
result += "Logged In";
}
if (auto_connect_reasons & AutoConnectHandler::AutoConnectReason::
AUTO_CONNECT_REASON_POLICY_APPLIED) {
if (!result.empty())
result += ", ";
result += "Policy Applied";
}
if (auto_connect_reasons & AutoConnectHandler::AutoConnectReason::
AUTO_CONNECT_REASON_CERTIFICATE_RESOLVED) {
if (!result.empty())
result += ", ";
result += "Certificate resolved";
}
return result;
}
}
AutoConnectHandler::AutoConnectHandler()
: client_cert_resolver_(nullptr),
request_best_connection_pending_(false),
device_policy_applied_(false),
user_policy_applied_(false),
client_certs_resolved_(false),
applied_autoconnect_policy_on_wifi(false),
applied_autoconnect_policy_on_cellular(false),
auto_connect_reasons_(0) {}
AutoConnectHandler::~AutoConnectHandler() {
if (LoginState::IsInitialized())
LoginState::Get()->RemoveObserver(this);
if (client_cert_resolver_)
client_cert_resolver_->RemoveObserver(this);
if (network_connection_handler_)
network_connection_handler_->RemoveObserver(this);
if (managed_configuration_handler_)
managed_configuration_handler_->RemoveObserver(this);
}
void AutoConnectHandler::Init(
ClientCertResolver* client_cert_resolver,
NetworkConnectionHandler* network_connection_handler,
NetworkStateHandler* network_state_handler,
ManagedNetworkConfigurationHandler* managed_network_configuration_handler) {
if (LoginState::IsInitialized())
LoginState::Get()->AddObserver(this);
client_cert_resolver_ = client_cert_resolver;
if (client_cert_resolver_)
client_cert_resolver_->AddObserver(this);
network_connection_handler_ = network_connection_handler;
if (network_connection_handler_)
network_connection_handler_->AddObserver(this);
network_state_handler_ = network_state_handler;
if (network_state_handler_) {
network_state_handler_observer_.Observe(network_state_handler_.get());
CheckWifiEnabled();
}
managed_configuration_handler_ = managed_network_configuration_handler;
if (managed_configuration_handler_)
managed_configuration_handler_->AddObserver(this);
if (LoginState::IsInitialized())
LoggedInStateChanged();
}
void AutoConnectHandler::LoggedInStateChanged() {
if (!LoginState::Get()->IsUserLoggedIn())
return;
DisconnectWiFiIfPolicyRequires();
DisconnectCellularIfPolicyRequires();
RequestBestConnection(AutoConnectReason::AUTO_CONNECT_REASON_LOGGED_IN);
}
ConnectToNetworkRequestVerdict AutoConnectHandler::ConnectToNetworkRequested(
const std::string& ) {
if (ShouldEnforceIsAllowOnlyPolicyWiFiToConnectIfAvailable() &&
!initial_scan_done_) {
return ConnectToNetworkRequestVerdict::kVetoWaitingForScan;
}
request_best_connection_pending_ = false;
return ConnectToNetworkRequestVerdict::kProceed;
}
void AutoConnectHandler::PoliciesApplied(const std::string& userhash) {
if (userhash.empty()) {
device_policy_applied_ = true;
} else {
user_policy_applied_ = true;
}
DisconnectWiFiIfPolicyRequires();
DisconnectCellularIfPolicyRequires();
if (managed_configuration_handler_->HasAnyPolicyNetwork(userhash)) {
RequestBestConnection(
AutoConnectReason::AUTO_CONNECT_REASON_POLICY_APPLIED);
} else {
CheckBestConnection();
}
}
void AutoConnectHandler::ScanCompleted(const DeviceState* device) {
if (!HasTypeWifi(device)) {
return;
}
initial_scan_done_ = true;
const NetworkState* managed_network =
network_state_handler_->GetAvailableManagedWifiNetwork();
if (ShouldEnforceIsAllowOnlyPolicyWiFiToConnectIfAvailable() &&
managed_network) {
const NetworkState* connected_network =
network_state_handler_->ConnectedNetworkByType(
NetworkTypePattern::WiFi());
if (connected_network && !connected_network->IsManagedByPolicy()) {
network_connection_handler_->ConnectToNetwork(
managed_network->path(), base::DoNothing(),
base::BindOnce(&ConnectToNetworkErrorCallback), false,
ConnectCallbackMode::ON_COMPLETED);
return;
}
}
}
void AutoConnectHandler::DevicePropertiesUpdated(const DeviceState* device) {
if (!HasTypeWifi(device)) {
return;
}
CheckWifiEnabled();
}
void AutoConnectHandler::DeviceListChanged() {
CheckWifiEnabled();
}
void AutoConnectHandler::ResolveRequestCompleted(
bool network_properties_changed) {
client_certs_resolved_ = true;
if (network_properties_changed) {
RequestBestConnection(
AutoConnectReason::AUTO_CONNECT_REASON_CERTIFICATE_RESOLVED);
} else {
CheckBestConnection();
}
}
void AutoConnectHandler::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void AutoConnectHandler::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void AutoConnectHandler::NotifyAutoConnectInitiatedForTest(
int auto_connect_reasons) {
NotifyAutoConnectInitiated(auto_connect_reasons);
}
void AutoConnectHandler::NotifyAutoConnectInitiated(int auto_connect_reasons) {
NET_LOG(EVENT) << "AutoConnectInitiated ["
<< AutoConnectReasonsToString(auto_connect_reasons_) << "]";
for (auto& observer : observer_list_) {
observer.OnAutoConnectedInitiated(auto_connect_reasons);
}
if (network_connection_handler_) {
network_connection_handler_->OnAutoConnectedInitiated(auto_connect_reasons);
}
}
void AutoConnectHandler::RequestBestConnection(
AutoConnectReason auto_connect_reason) {
request_best_connection_pending_ = true;
auto_connect_reasons_ |= auto_connect_reason;
CheckBestConnection();
}
void AutoConnectHandler::CheckBestConnection() {
if (!request_best_connection_pending_)
return;
bool policy_application_running =
managed_configuration_handler_->IsAnyPolicyApplicationRunning();
bool client_cert_resolve_task_running =
client_cert_resolver_->IsAnyResolveTaskRunning();
VLOG(2) << "device policy applied: " << device_policy_applied_
<< "\nuser policy applied: " << user_policy_applied_
<< "\npolicy application running: " << policy_application_running
<< "\nclient cert patterns resolved: " << client_certs_resolved_
<< "\nclient cert resolve task running: "
<< client_cert_resolve_task_running;
if (!device_policy_applied_ || policy_application_running ||
client_cert_resolve_task_running) {
return;
}
if (LoginState::Get()->IsUserLoggedIn()) {
if (!user_policy_applied_ || !client_certs_resolved_)
return;
initial_scan_done_ = false;
}
request_best_connection_pending_ = false;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&AutoConnectHandler::CallShillScanAndConnectToBestServices,
weak_ptr_factory_.GetWeakPtr()));
}
void AutoConnectHandler::DisconnectWiFiIfPolicyRequires() {
if (!device_policy_applied_ || !user_policy_applied_)
return;
std::vector<std::string> blocked_hex_ssids =
managed_configuration_handler_->GetBlockedHexSSIDs();
bool only_managed_wifi =
managed_configuration_handler_->AllowOnlyPolicyWiFiToConnect();
bool only_managed_autoconnect =
managed_configuration_handler_->AllowOnlyPolicyNetworksToAutoconnect();
bool available_only =
managed_configuration_handler_
->AllowOnlyPolicyWiFiToConnectIfAvailable() &&
network_state_handler_->GetAvailableManagedWifiNetwork();
if (applied_autoconnect_policy_on_wifi)
only_managed_autoconnect = false;
else
applied_autoconnect_policy_on_wifi = only_managed_autoconnect;
if (!only_managed_wifi && !only_managed_autoconnect &&
blocked_hex_ssids.empty() && !available_only) {
return;
}
NetworkStateHandler::NetworkStateList networks;
network_state_handler_->GetNetworkListByType(
NetworkTypePattern::WiFi(),
false, false, 0, &networks);
DisconnectAndRemoveConfigOrDisableAutoConnect(
networks, only_managed_autoconnect, available_only);
}
void AutoConnectHandler::DisconnectCellularIfPolicyRequires() {
bool only_managed_cellular =
managed_configuration_handler_->AllowOnlyPolicyCellularNetworks();
bool only_managed_autoconnect =
managed_configuration_handler_->AllowOnlyPolicyNetworksToAutoconnect();
if (applied_autoconnect_policy_on_cellular)
only_managed_autoconnect = false;
else
applied_autoconnect_policy_on_cellular = only_managed_autoconnect;
if (!only_managed_cellular && !only_managed_autoconnect) {
return;
}
NetworkStateHandler::NetworkStateList networks;
network_state_handler_->GetNetworkListByType(
NetworkTypePattern::Cellular(),
false, false, 0, &networks);
DisconnectAndRemoveConfigOrDisableAutoConnect(
networks, only_managed_autoconnect, false);
}
void AutoConnectHandler::DisconnectAndRemoveConfigOrDisableAutoConnect(
const NetworkStateHandler::NetworkStateList& networks,
bool only_managed_autoconnect,
bool available_only) {
for (const NetworkState* network : networks) {
if (network->IsManagedByPolicy())
continue;
bool is_cellular_type = network->type() == shill::kTypeCellular;
if (network->blocked_by_policy()) {
if (network->IsConnectingOrConnected())
DisconnectNetwork(network);
if (is_cellular_type) {
DisableAutoconnectForNetwork(network->path(),
::onc::network_config::kCellular);
}
bool network_config_allowed =
available_only &&
!base::Contains(managed_configuration_handler_->GetBlockedHexSSIDs(),
network->GetHexSsid());
bool shouldRemoveWifiConfig =
(HasTypeWifi(network) && network->IsInProfile()) &&
!network_config_allowed;
if (shouldRemoveWifiConfig) {
RemoveNetworkConfigurationForNetwork(network->path());
}
} else if (only_managed_autoconnect) {
if (network->IsConnectingOrConnected())
DisconnectNetwork(network);
if (network->IsInProfile())
DisableAutoconnectForNetwork(
network->path(), is_cellular_type ? ::onc::network_config::kCellular
: ::onc::network_config::kWiFi);
}
}
}
void AutoConnectHandler::DisconnectNetwork(const NetworkState* network) {
NET_LOG(EVENT) << "Disconnect forced by policy for: "
<< NetworkPathId(network->path());
network_connection_handler_->DisconnectNetwork(
network->path(),
base::BindOnce(&RecordDisconnectByPolicyResult, network,
true),
base::BindOnce(&DisconnectErrorCallback, network));
}
void AutoConnectHandler::RemoveNetworkConfigurationForNetwork(
const std::string& service_path) {
NET_LOG(EVENT) << "Remove configuration forced by policy for: "
<< NetworkPathId(service_path);
managed_configuration_handler_->RemoveConfiguration(
service_path, base::DoNothing(),
base::BindOnce(&RemoveNetworkConfigurationErrorCallback));
}
void AutoConnectHandler::DisableAutoconnectForNetwork(
const std::string& service_path,
const std::string& network_type) {
NET_LOG(EVENT) << "Disable auto-connect forced by policy: "
<< NetworkPathId(service_path);
base::Value::Dict properties;
std::string autoconnect_path;
if (network_type == ::onc::network_config::kWiFi) {
autoconnect_path = base::StrCat(
{::onc::network_config::kWiFi, ".", ::onc::wifi::kAutoConnect});
} else if (network_type == ::onc::network_config::kCellular) {
autoconnect_path = base::StrCat(
{::onc::network_config::kCellular, ".", ::onc::cellular::kAutoConnect});
} else {
NOTREACHED();
}
properties.SetByDottedPath(autoconnect_path, false);
managed_configuration_handler_->SetProperties(
service_path, properties, base::DoNothing(),
base::BindOnce(&SetPropertiesErrorCallback));
}
void AutoConnectHandler::CallShillScanAndConnectToBestServices() {
NET_LOG(EVENT) << "ScanAndConnectToBestServices ["
<< AutoConnectReasonsToString(auto_connect_reasons_) << "]";
ShillManagerClient::Get()->ScanAndConnectToBestServices(
base::BindOnce(&AutoConnectHandler::NotifyAutoConnectInitiated,
weak_ptr_factory_.GetWeakPtr(), auto_connect_reasons_),
base::BindOnce(&network_handler::ShillErrorCallbackFunction,
"ConnectToBestServices Failed", "",
network_handler::ErrorCallback()));
}
bool AutoConnectHandler::
ShouldEnforceIsAllowOnlyPolicyWiFiToConnectIfAvailable() {
return device_policy_applied_ && user_policy_applied_ &&
managed_configuration_handler_
->AllowOnlyPolicyWiFiToConnectIfAvailable();
}
void AutoConnectHandler::CheckWifiEnabled() {
const bool enabled =
network_state_handler_->IsTechnologyEnabled(NetworkTypePattern::WiFi());
if (wifi_enabled_ != enabled) {
initial_scan_done_ = false;
}
wifi_enabled_ = enabled;
}
}