// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/types/cxx23_to_underlying.h"
#include "base/values.h"
#include "chromeos/ash/components/network/cellular_utils.h"
#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
#include "chromeos/ash/components/network/network_configuration_handler.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_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "components/onc/onc_constants.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
namespace ash {
namespace {
const char kNetworkMetadataPref[] = "network_metadata";
const char kLastConnectedTimestampPref[] = "last_connected_timestamp";
const char kCreationTimestamp[] = "creation_timestamp";
const char kIsFromSync[] = "is_from_sync";
const char kOwner[] = "owner";
const char kExternalModifications[] = "external_modifications";
const char kBadPassword[] = "bad_password";
const char kCustomApnList[] = "custom_apn_list";
const char kCustomApnListV2[] = "custom_apn_list_v2";
const char kHasFixedHiddenNetworks[] =
"metadata_store.has_fixed_hidden_networks";
const char kDayOfTrafficCountersAutoReset[] =
"day_of_traffic_counters_auto_reset";
const char kUserTextMessageSuppressionState[] =
"user_text_message_suppression_state";
constexpr base::TimeDelta kDefaultOverrideAge = base::Days(1);
// Wait two weeks before overwriting the creation timestamp for a given
// network.
constexpr base::TimeDelta kTwoWeeks = base::Days(14);
std::string GetPath(const std::string& guid, const std::string& subkey) {
return base::StringPrintf("%s.%s", guid.c_str(), subkey.c_str());
}
base::Value::List CreateOrCloneListValue(const base::Value::List* list) {
if (list)
return list->Clone();
return base::Value::List();
}
bool IsApnListValid(const base::Value::List& list) {
for (const base::Value& apn : list) {
if (!apn.is_dict())
return false;
if (!apn.GetDict().Find(::onc::cellular_apn::kAccessPointName))
return false;
}
return true;
}
base::TimeDelta ComputeMigrationMinimumAge() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kHiddenNetworkMigrationAge)) {
return kTwoWeeks;
}
int age_in_days = -1;
const std::string ascii =
command_line->GetSwitchValueASCII(switches::kHiddenNetworkMigrationAge);
if (ascii.empty() || !base::StringToInt(ascii, &age_in_days) ||
age_in_days < 0) {
return kDefaultOverrideAge;
}
return base::Days(age_in_days);
}
} // namespace
// static
void NetworkMetadataStore::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(kNetworkMetadataPref);
registry->RegisterBooleanPref(kHasFixedHiddenNetworks,
/*default_value=*/false);
}
NetworkMetadataStore::NetworkMetadataStore(
NetworkConfigurationHandler* network_configuration_handler,
NetworkConnectionHandler* network_connection_handler,
NetworkStateHandler* network_state_handler,
ManagedNetworkConfigurationHandler* managed_network_configuration_handler,
PrefService* profile_pref_service,
PrefService* device_pref_service,
bool is_enterprise_managed)
: network_configuration_handler_(network_configuration_handler),
network_connection_handler_(network_connection_handler),
network_state_handler_(network_state_handler),
managed_network_configuration_handler_(
managed_network_configuration_handler),
profile_pref_service_(profile_pref_service),
device_pref_service_(device_pref_service),
is_enterprise_managed_(is_enterprise_managed) {
if (network_connection_handler_) {
network_connection_handler_->AddObserver(this);
}
if (network_configuration_handler_) {
network_configuration_handler_->AddObserver(this);
}
if (network_state_handler_) {
network_state_handler_observer_.Observe(network_state_handler_.get());
}
if (LoginState::IsInitialized()) {
LoginState::Get()->AddObserver(this);
}
}
NetworkMetadataStore::~NetworkMetadataStore() {
if (network_connection_handler_) {
network_connection_handler_->RemoveObserver(this);
}
if (network_configuration_handler_) {
network_configuration_handler_->RemoveObserver(this);
}
if (LoginState::IsInitialized()) {
LoginState::Get()->RemoveObserver(this);
}
}
void NetworkMetadataStore::LoggedInStateChanged() {
OwnSharedNetworksOnFirstUserLogin();
}
void NetworkMetadataStore::NetworkListChanged() {
// Ensure that user networks have been loaded from Shill before querying.
if (!network_state_handler_->IsProfileNetworksLoaded()) {
has_profile_loaded_ = false;
return;
}
if (has_profile_loaded_) {
return;
}
has_profile_loaded_ = true;
FixSyncedHiddenNetworks();
LogHiddenNetworkAge();
}
void NetworkMetadataStore::OwnSharedNetworksOnFirstUserLogin() {
if (is_enterprise_managed_ || !network_state_handler_ ||
!user_manager::UserManager::IsInitialized()) {
return;
}
const user_manager::UserManager* user_manager =
user_manager::UserManager::Get();
if (!user_manager->IsCurrentUserNew() ||
!user_manager->IsCurrentUserOwner()) {
return;
}
NET_LOG(EVENT) << "Taking ownership of shared networks.";
NetworkStateHandler::NetworkStateList networks;
network_state_handler_->GetNetworkListByType(
NetworkTypePattern::WiFi(), /*configured_only=*/true,
/*visible_only=*/false, /*limit=*/0, &networks);
for (const NetworkState* network : networks) {
if (network->IsPrivate()) {
continue;
}
SetIsCreatedByUser(network->guid());
}
}
void NetworkMetadataStore::FixSyncedHiddenNetworks() {
if (HasFixedHiddenNetworks()) {
return;
}
NetworkStateHandler::NetworkStateList networks;
network_state_handler_->GetNetworkListByType(
NetworkTypePattern::WiFi(), /*configured_only=*/true,
/*visible_only=*/false, /*limit=*/0, &networks);
NET_LOG(EVENT) << "Updating networks from sync to disable HiddenSSID.";
int total_count = 0;
for (const NetworkState* network : networks) {
if (!network->hidden_ssid()) {
continue;
}
if (!GetIsConfiguredBySync(network->guid())) {
continue;
}
total_count++;
auto dict = base::Value::Dict().Set(shill::kWifiHiddenSsid, false);
network_configuration_handler_->SetShillProperties(
network->path(), std::move(dict), base::DoNothing(),
base::BindOnce(&NetworkMetadataStore::OnDisableHiddenError,
weak_ptr_factory_.GetWeakPtr()));
}
profile_pref_service_->SetBoolean(kHasFixedHiddenNetworks, true);
base::UmaHistogramCounts1000("Network.Wifi.Synced.Hidden.Fixed", total_count);
}
void NetworkMetadataStore::LogHiddenNetworkAge() {
NetworkStateHandler::NetworkStateList networks;
network_state_handler_->GetNetworkListByType(
NetworkTypePattern::WiFi(), /*configured_only=*/true,
/*visible_only=*/false, /*limit=*/0, &networks);
for (const NetworkState* network : networks) {
if (!network->hidden_ssid()) {
continue;
}
base::TimeDelta timestamp = GetLastConnectedTimestamp(network->guid());
if (!timestamp.is_zero()) {
int days = base::Time::Now().ToDeltaSinceWindowsEpoch().InDays() -
timestamp.InDays();
base::UmaHistogramCounts10000("Network.Shill.WiFi.Hidden.LastConnected",
days);
}
base::UmaHistogramBoolean("Network.Shill.WiFi.Hidden.EverConnected",
!timestamp.is_zero());
}
}
bool NetworkMetadataStore::HasFixedHiddenNetworks() {
if (!profile_pref_service_) {
// A user must be logged in to fix hidden networks.
return true;
}
return profile_pref_service_->GetBoolean(kHasFixedHiddenNetworks);
}
void NetworkMetadataStore::OnDisableHiddenError(const std::string& error_name) {
NET_LOG(EVENT) << "Failed to disable HiddenSSID on synced network. Error: "
<< error_name;
}
void NetworkMetadataStore::ConnectSucceeded(const std::string& service_path) {
const NetworkState* network =
network_state_handler_->GetNetworkState(service_path);
if (!network || network->type() != shill::kTypeWifi) {
return;
}
bool is_first_connection =
GetLastConnectedTimestamp(network->guid()).is_zero();
SetLastConnectedTimestamp(network->guid(),
base::Time::Now().ToDeltaSinceWindowsEpoch());
SetPref(network->guid(), kBadPassword, base::Value(false));
if (is_first_connection) {
for (auto& observer : observers_) {
observer.OnFirstConnectionToNetwork(network->guid());
}
}
}
void NetworkMetadataStore::ConnectFailed(const std::string& service_path,
const std::string& error_name) {
const NetworkState* network =
network_state_handler_->GetNetworkState(service_path);
// Only set kBadPassword for Wi-Fi networks which have never had a successful
// connection with the current password. |error_name| is always set to
// "connect-failed", network->GetError() contains the real cause.
if (!network || network->type() != shill::kTypeWifi ||
network->GetError() != shill::kErrorBadPassphrase ||
!GetLastConnectedTimestamp(network->guid()).is_zero()) {
return;
}
SetPref(network->guid(), kBadPassword, base::Value(true));
}
void NetworkMetadataStore::OnConfigurationCreated(
const std::string& service_path,
const std::string& guid) {
SetIsCreatedByUser(guid);
}
void NetworkMetadataStore::SetIsCreatedByUser(const std::string& network_guid) {
if (!user_manager::UserManager::IsInitialized())
return;
const user_manager::User* user =
user_manager::UserManager::Get()->GetActiveUser();
if (!user) {
NET_LOG(EVENT)
<< "Network added with no active user, owner metadata not recorded.";
return;
}
SetPref(network_guid, kOwner, base::Value(user->username_hash()));
for (auto& observer : observers_) {
observer.OnNetworkCreated(network_guid);
}
}
void NetworkMetadataStore::UpdateExternalModifications(
const std::string& network_guid,
const std::string& field) {
const base::Value::List* fields =
GetListPref(network_guid, kExternalModifications);
const bool contains_field = fields && base::Contains(*fields, field);
if (GetIsCreatedByUser(network_guid)) {
if (contains_field) {
base::Value::List writeable_fields = CreateOrCloneListValue(fields);
writeable_fields.EraseValue(base::Value(field));
SetPref(network_guid, kExternalModifications,
base::Value(std::move(writeable_fields)));
}
} else if (!contains_field) {
base::Value::List writeable_fields = CreateOrCloneListValue(fields);
writeable_fields.Append(field);
SetPref(network_guid, kExternalModifications,
base::Value(std::move(writeable_fields)));
}
}
void NetworkMetadataStore::OnConfigurationModified(
const std::string& service_path,
const std::string& guid,
const base::Value::Dict* set_properties) {
if (!set_properties) {
return;
}
SetPref(guid, kIsFromSync, base::Value(false));
if (set_properties->Find(shill::kProxyConfigProperty)) {
UpdateExternalModifications(guid, shill::kProxyConfigProperty);
}
if (set_properties->FindByDottedPath(
base::StringPrintf("%s.%s", shill::kStaticIPConfigProperty,
shill::kNameServersProperty))) {
UpdateExternalModifications(guid, shill::kNameServersProperty);
}
if (set_properties->Find(shill::kPassphraseProperty)) {
// Only clear last connected if the passphrase changes. Other settings
// (autoconnect, dns, etc.) won't affect the ability to connect to a
// network.
SetPref(guid, kLastConnectedTimestampPref, base::Value(0));
// Whichever user supplied the password is the "owner".
SetIsCreatedByUser(guid);
}
for (auto& observer : observers_) {
observer.OnNetworkUpdate(guid, set_properties);
}
}
void NetworkMetadataStore::OnConfigurationRemoved(
const std::string& service_path,
const std::string& network_guid) {
RemoveNetworkFromPref(network_guid, device_pref_service_);
RemoveNetworkFromPref(network_guid, profile_pref_service_);
}
void NetworkMetadataStore::RemoveNetworkFromPref(
const std::string& network_guid,
PrefService* pref_service) {
if (!pref_service) {
return;
}
const base::Value::Dict& dict = pref_service->GetDict(kNetworkMetadataPref);
if (!dict.contains(network_guid)) {
return;
}
base::Value::Dict writeable_dict = dict.Clone();
if (!writeable_dict.Remove(network_guid)) {
return;
}
pref_service->SetDict(kNetworkMetadataPref, std::move(writeable_dict));
}
void NetworkMetadataStore::SetIsConfiguredBySync(
const std::string& network_guid) {
SetPref(network_guid, kIsFromSync, base::Value(true));
}
base::TimeDelta NetworkMetadataStore::GetLastConnectedTimestamp(
const std::string& network_guid) {
const base::Value* timestamp =
GetPref(network_guid, kLastConnectedTimestampPref);
if (!timestamp || !timestamp->is_double()) {
return base::TimeDelta();
}
return base::Milliseconds(timestamp->GetDouble());
}
void NetworkMetadataStore::SetLastConnectedTimestamp(
const std::string& network_guid,
const base::TimeDelta& timestamp) {
double timestamp_f = timestamp.InMillisecondsF();
SetPref(network_guid, kLastConnectedTimestampPref, base::Value(timestamp_f));
}
base::Time NetworkMetadataStore::UpdateAndRetrieveWiFiTimestamp(
const std::string& network_guid) {
const NetworkState* network =
network_state_handler_->GetNetworkStateFromGuid(network_guid);
if (!network || network->GetNetworkTechnologyType() !=
NetworkState::NetworkTechnologyType::kWiFi) {
return base::Time::Now().UTCMidnight();
}
const base::Value* creation_timestamp_pref =
GetPref(network_guid, kCreationTimestamp);
const base::Time current_timestamp = base::Time::Now().UTCMidnight();
if (!creation_timestamp_pref) {
SetPref(network_guid, kCreationTimestamp,
base::Value(current_timestamp.InSecondsFSinceUnixEpoch()));
return current_timestamp;
}
const base::Time creation_timestamp = base::Time::FromSecondsSinceUnixEpoch(
creation_timestamp_pref->GetDouble());
const base::TimeDelta minimum_age = ComputeMigrationMinimumAge();
if (creation_timestamp + minimum_age <= current_timestamp) {
SetPref(network_guid, kCreationTimestamp, base::Value(0.0));
return base::Time::UnixEpoch();
}
return creation_timestamp;
}
bool NetworkMetadataStore::GetIsConfiguredBySync(
const std::string& network_guid) {
const base::Value* is_from_sync = GetPref(network_guid, kIsFromSync);
if (!is_from_sync) {
return false;
}
return is_from_sync->GetBool();
}
bool NetworkMetadataStore::GetIsCreatedByUser(const std::string& network_guid) {
const NetworkState* network =
network_state_handler_->GetNetworkStateFromGuid(network_guid);
if (network && network->IsPrivate())
return true;
const base::Value* owner = GetPref(network_guid, kOwner);
if (!owner) {
return false;
}
const user_manager::User* user =
user_manager::UserManager::Get()->GetActiveUser();
if (!user) {
return false;
}
return owner->GetString() == user->username_hash();
}
bool NetworkMetadataStore::GetIsFieldExternallyModified(
const std::string& network_guid,
const std::string& field) {
const base::Value::List* fields =
GetListPref(network_guid, kExternalModifications);
return fields && base::Contains(*fields, field);
}
bool NetworkMetadataStore::GetHasBadPassword(const std::string& network_guid) {
const base::Value* has_bad_password = GetPref(network_guid, kBadPassword);
// If the pref is not set, default to false.
if (!has_bad_password) {
return false;
}
return has_bad_password->GetBool();
}
void NetworkMetadataStore::SetCustomApnList(const std::string& network_guid,
base::Value::List list) {
if (ash::features::IsApnRevampEnabled()) {
if (!IsApnListValid(list)) {
NET_LOG(ERROR) << "network_guid: " << network_guid << std::endl
<< "Invalid list passed to SetCustomApnList():" << list;
return;
}
SetPref(network_guid, kCustomApnListV2, base::Value(std::move(list)));
return;
}
SetPref(network_guid, kCustomApnList, base::Value(std::move(list)));
}
const base::Value::List* NetworkMetadataStore::GetCustomApnList(
const std::string& network_guid) {
if (ash::features::IsApnRevampEnabled()) {
if (const base::Value* pref = GetPref(network_guid, kCustomApnListV2)) {
return pref->GetIfList();
}
return nullptr;
}
if (const base::Value* pref = GetPref(network_guid, kCustomApnList)) {
return pref->GetIfList();
}
return nullptr;
}
const base::Value::List* NetworkMetadataStore::GetPreRevampCustomApnList(
const std::string& network_guid) {
DCHECK(ash::features::IsApnRevampEnabled());
if (const base::Value* pref = GetPref(network_guid, kCustomApnList)) {
return pref->GetIfList();
}
return nullptr;
}
void NetworkMetadataStore::SetDayOfTrafficCountersAutoReset(
const std::string& network_guid,
const std::optional<int>& day) {
auto value = day.has_value() ? base::Value(day.value()) : base::Value();
SetPref(network_guid, kDayOfTrafficCountersAutoReset, std::move(value));
}
const base::Value* NetworkMetadataStore::GetDayOfTrafficCountersAutoReset(
const std::string& network_guid) {
return GetPref(network_guid, kDayOfTrafficCountersAutoReset);
}
void NetworkMetadataStore::SetSecureDnsTemplatesWithIdentifiersActive(
bool active) {
if (secure_dns_templates_with_identifiers_active_ == active) {
return;
}
secure_dns_templates_with_identifiers_active_ = active;
managed_network_configuration_handler_
->OnEnterpriseMonitoredWebPoliciesApplied();
}
void NetworkMetadataStore::SetReportXdrEventsEnabled(bool enabled) {
if (report_xdr_events_enabled_ == enabled) {
return;
}
report_xdr_events_enabled_ = enabled;
managed_network_configuration_handler_
->OnEnterpriseMonitoredWebPoliciesApplied();
}
void NetworkMetadataStore::SetUserTextMessageSuppressionState(
const std::string& network_guid,
const UserTextMessageSuppressionState& state) {
SetPref(network_guid, kUserTextMessageSuppressionState,
base::Value(base::to_underlying(state)));
CellularNetworkMetricsLogger::LogUserTextMessageSuppressionState(state);
}
UserTextMessageSuppressionState
NetworkMetadataStore::GetUserTextMessageSuppressionState(
const std::string& network_guid) {
const base::Value* state_value =
GetPref(network_guid, kUserTextMessageSuppressionState);
if (!state_value || !state_value->is_int()) {
return UserTextMessageSuppressionState::kAllow;
}
if (base::to_underlying(UserTextMessageSuppressionState::kAllow) ==
state_value->GetInt()) {
return UserTextMessageSuppressionState::kAllow;
} else if (base::to_underlying(UserTextMessageSuppressionState::kSuppress) ==
state_value->GetInt()) {
return UserTextMessageSuppressionState::kSuppress;
}
NOTREACHED_IN_MIGRATION();
return UserTextMessageSuppressionState::kAllow;
}
void NetworkMetadataStore::SetPref(const std::string& network_guid,
const std::string& key,
base::Value value) {
const NetworkState* network =
network_state_handler_->GetNetworkStateFromGuid(network_guid);
if (network && network->IsPrivate() && profile_pref_service_) {
base::Value::Dict profile_dict =
profile_pref_service_->GetDict(kNetworkMetadataPref).Clone();
profile_dict.SetByDottedPath(GetPath(network_guid, key), std::move(value));
profile_pref_service_->SetDict(kNetworkMetadataPref,
std::move(profile_dict));
return;
}
base::Value::Dict device_dict =
device_pref_service_->GetDict(kNetworkMetadataPref).Clone();
device_dict.SetByDottedPath(GetPath(network_guid, key), std::move(value));
device_pref_service_->SetDict(kNetworkMetadataPref, std::move(device_dict));
}
const base::Value* NetworkMetadataStore::GetPref(
const std::string& network_guid,
const std::string& key) {
if (!network_state_handler_) {
return nullptr;
}
const NetworkState* network =
network_state_handler_->GetNetworkStateFromGuid(network_guid);
if (network && network->IsPrivate() && profile_pref_service_) {
const base::Value::Dict& profile_dict =
profile_pref_service_->GetDict(kNetworkMetadataPref);
const base::Value* value =
profile_dict.FindByDottedPath(GetPath(network_guid, key));
if (value)
return value;
}
const base::Value::Dict& device_dict =
device_pref_service_->GetDict(kNetworkMetadataPref);
return device_dict.FindByDottedPath(GetPath(network_guid, key));
}
const base::Value::List* NetworkMetadataStore::GetListPref(
const std::string& network_guid,
const std::string& key) {
const base::Value* pref = GetPref(network_guid, key);
if (!pref)
return nullptr;
return pref->GetIfList();
}
void NetworkMetadataStore::AddObserver(NetworkMetadataObserver* observer) {
observers_.AddObserver(observer);
}
void NetworkMetadataStore::RemoveObserver(NetworkMetadataObserver* observer) {
observers_.RemoveObserver(observer);
}
} // namespace ash