blob: 3d60186954afa4e070686c620e95fc2da4f96236 [file] [log] [blame]
// Copyright (c) 2020 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 "chromeos/ash/components/network/network_metadata_store.h"
#include "ash/constants/ash_features.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.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/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 kHasFixedHiddenNetworks[] =
"metadata_store.has_fixed_hidden_networks";
const char kEnableTrafficCountersAutoReset[] =
"enable_traffic_counters_auto_reset";
const char kDayOfTrafficCountersAutoReset[] =
"day_of_traffic_counters_auto_reset";
// 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 CreateOrCloneListValue(const base::Value* list) {
if (list)
return list->Clone();
return base::Value(base::Value::Type::LIST);
}
bool ListContains(const base::Value* list, const std::string& value) {
if (!list)
return false;
base::Value::ConstListView list_view = list->GetListDeprecated();
return std::find(list_view.begin(), list_view.end(), base::Value(value)) !=
list_view.end();
}
} // 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,
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),
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_);
}
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++;
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetBoolKey(shill::kWifiHiddenSsid, false);
network_configuration_handler_->SetShillProperties(
network->path(), 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* fields = GetPref(network_guid, kExternalModifications);
if (GetIsCreatedByUser(network_guid)) {
if (ListContains(fields, field)) {
base::Value writeable_fields = CreateOrCloneListValue(fields);
writeable_fields.EraseListValue(base::Value(field));
SetPref(network_guid, kExternalModifications,
std::move(writeable_fields));
}
} else if (!ListContains(fields, field)) {
base::Value writeable_fields = CreateOrCloneListValue(fields);
writeable_fields.Append(base::Value(field));
SetPref(network_guid, kExternalModifications, std::move(writeable_fields));
}
}
void NetworkMetadataStore::OnConfigurationModified(
const std::string& service_path,
const std::string& guid,
const base::Value* set_properties) {
if (!set_properties) {
return;
}
SetPref(guid, kIsFromSync, base::Value(false));
if (set_properties->FindKey(shill::kProxyConfigProperty)) {
UpdateExternalModifications(guid, shill::kProxyConfigProperty);
}
if (set_properties->FindPath(
base::StringPrintf("%s.%s", shill::kStaticIPConfigProperty,
shill::kNameServersProperty))) {
UpdateExternalModifications(guid, shill::kNameServersProperty);
}
if (set_properties->FindKey(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 = pref_service->GetDictionary(kNetworkMetadataPref);
if (!dict || !dict->FindKey(network_guid)) {
return;
}
base::Value writeable_dict = dict->Clone();
if (!writeable_dict.RemoveKey(network_guid)) {
return;
}
pref_service->Set(kNetworkMetadataPref, 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) {
DCHECK(base::FeatureList::IsEnabled(features::kHiddenNetworkMigration));
const NetworkState* network =
network_state_handler_->GetNetworkStateFromGuid(network_guid);
if (!network || network->GetNetworkTechnologyType() !=
NetworkState::NetworkTechnologyType::kWiFi) {
return base::Time::UnixEpoch();
}
const base::Value* creation_timestamp =
GetPref(network_guid, kCreationTimestamp);
const base::Time current_timestamp = base::Time::Now().UTCMidnight();
if (creation_timestamp &&
base::Time::FromDoubleT(creation_timestamp->GetDouble()) + kTwoWeeks <=
current_timestamp) {
SetPref(network_guid, kCreationTimestamp,
base::Value(base::Time::UnixEpoch().ToDoubleT()));
return base::Time::UnixEpoch();
}
if (!creation_timestamp) {
SetPref(network_guid, kCreationTimestamp,
base::Value(current_timestamp.ToDoubleT()));
}
return current_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* fields = GetPref(network_guid, kExternalModifications);
return ListContains(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) {
SetPref(network_guid, kCustomApnList, std::move(list));
}
const base::Value* NetworkMetadataStore::GetCustomAPNList(
const std::string& network_guid) {
return GetPref(network_guid, kCustomApnList);
}
void NetworkMetadataStore::SetEnableTrafficCountersAutoReset(
const std::string& network_guid,
bool enable) {
SetPref(network_guid, kEnableTrafficCountersAutoReset, base::Value(enable));
}
void NetworkMetadataStore::SetDayOfTrafficCountersAutoReset(
const std::string& network_guid,
const absl::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::GetEnableTrafficCountersAutoReset(
const std::string& network_guid) {
return GetPref(network_guid, kEnableTrafficCountersAutoReset);
}
const base::Value* NetworkMetadataStore::GetDayOfTrafficCountersAutoReset(
const std::string& network_guid) {
return GetPref(network_guid, kDayOfTrafficCountersAutoReset);
}
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 profile_dict =
profile_pref_service_->GetDictionary(kNetworkMetadataPref)->Clone();
profile_dict.SetPath(GetPath(network_guid, key), std::move(value));
profile_pref_service_->Set(kNetworkMetadataPref, profile_dict);
return;
}
base::Value device_dict =
device_pref_service_->GetDictionary(kNetworkMetadataPref)->Clone();
device_dict.SetPath(GetPath(network_guid, key), std::move(value));
device_pref_service_->Set(kNetworkMetadataPref, 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* profile_dict =
profile_pref_service_->GetDictionary(kNetworkMetadataPref);
const base::Value* value =
profile_dict->FindPath(GetPath(network_guid, key));
if (value)
return value;
}
const base::Value* device_dict =
device_pref_service_->GetDictionary(kNetworkMetadataPref);
return device_dict->FindPath(GetPath(network_guid, key));
}
void NetworkMetadataStore::AddObserver(NetworkMetadataObserver* observer) {
observers_.AddObserver(observer);
}
void NetworkMetadataStore::RemoveObserver(NetworkMetadataObserver* observer) {
observers_.RemoveObserver(observer);
}
} // namespace ash