#include "chrome/browser/ash/policy/networking/euicc_status_uploader.h"
#include "ash/constants/ash_features.h"
#include "base/json/json_string_value_serializer.h"
#include "base/metrics/histogram_functions.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/network/cellular_esim_profile_handler.h"
#include "chromeos/ash/components/network/managed_cellular_pref_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_handler.h"
#include "components/onc/onc_constants.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace policy {
namespace {
const char kLastUploadedEuiccStatusEuiccCountKey[] = "euicc_count";
const char kLastUploadedEuiccStatusESimProfilesKey[] = "esim_profiles";
const char kLastUploadedEuiccStatusESimProfilesIccidKey[] = "iccid";
const char kLastUploadedEuiccStatusESimProfilesNetworkNameKey[] =
"network_name";
const char kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey[] =
"smdp_activation_code";
const char kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey[] =
"smds_activation_code";
const net::BackoffEntry::Policy kBackOffPolicy = {
0,
base::Minutes(5).InMilliseconds(),
2,
0.5,
base::Hours(6).InMilliseconds(),
-1,
true,
};
bool IsDeviceManaged() {
return ::ash::DeviceSettingsService::IsInitialized() &&
::ash::DeviceSettingsService::Get()->policy_data() &&
::ash::DeviceSettingsService::Get()->policy_data()->state() ==
enterprise_management::PolicyData::ACTIVE;
}
}
const char EuiccStatusUploader::kLastUploadedEuiccStatusPref[] =
"esim.last_uploaded_euicc_status";
const char EuiccStatusUploader::kShouldSendClearProfilesRequestPref[] =
"esim.should_clear_profile_list";
EuiccStatusUploader::EuiccStatusUploader(CloudPolicyClient* client,
PrefService* local_state)
: EuiccStatusUploader(client,
local_state,
base::BindRepeating(&IsDeviceManaged)) {}
EuiccStatusUploader::EuiccStatusUploader(
CloudPolicyClient* client,
PrefService* local_state,
IsDeviceActiveCallback is_device_active_callback)
: client_(client),
local_state_(local_state),
is_device_managed_callback_(std::move(is_device_active_callback)),
retry_entry_(&kBackOffPolicy) {
if (!ash::NetworkHandler::IsInitialized()) {
LOG(WARNING) << "NetworkHandler is not initialized.";
return;
}
hermes_manager_observation_.Observe(ash::HermesManagerClient::Get());
hermes_euicc_observation_.Observe(ash::HermesEuiccClient::Get());
cloud_policy_client_observation_.Observe(client_.get());
auto* network_handler = ash::NetworkHandler::Get();
network_handler->managed_cellular_pref_handler()->AddObserver(this);
managed_network_configuration_handler_ =
network_handler->managed_network_configuration_handler();
managed_network_configuration_handler_->AddObserver(this);
}
EuiccStatusUploader::~EuiccStatusUploader() {
if (ash::NetworkHandler::IsInitialized()) {
ash::NetworkHandler::Get()->managed_cellular_pref_handler()->RemoveObserver(
this);
}
OnManagedNetworkConfigurationHandlerShuttingDown();
}
void EuiccStatusUploader::RegisterLocalStatePrefs(
PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(kLastUploadedEuiccStatusPref,
PrefRegistry::NO_REGISTRATION_FLAGS);
registry->RegisterBooleanPref(kShouldSendClearProfilesRequestPref,
false);
}
std::unique_ptr<enterprise_management::UploadEuiccInfoRequest>
EuiccStatusUploader::ConstructRequestFromStatus(const base::Value::Dict& status,
bool clear_profile_list) {
auto upload_request =
std::make_unique<enterprise_management::UploadEuiccInfoRequest>();
upload_request->set_euicc_count(
status.FindInt(kLastUploadedEuiccStatusEuiccCountKey).value());
auto* mutable_esim_profiles = upload_request->mutable_esim_profiles();
for (const auto& esim_profile :
*status.FindListByDottedPath(kLastUploadedEuiccStatusESimProfilesKey)) {
const base::Value::Dict& esim_profile_dict = esim_profile.GetDict();
enterprise_management::ESimProfileInfo esim_profile_info;
esim_profile_info.set_iccid(*esim_profile_dict.FindString(
kLastUploadedEuiccStatusESimProfilesIccidKey));
const std::string* network_name = esim_profile_dict.FindString(
kLastUploadedEuiccStatusESimProfilesNetworkNameKey);
if (network_name && !network_name->empty()) {
esim_profile_info.set_name(*network_name);
}
const std::string* smdp_activation_code = esim_profile_dict.FindString(
kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey);
const std::string* smds_activation_code = esim_profile_dict.FindString(
kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey);
if (smdp_activation_code && !smdp_activation_code->empty()) {
esim_profile_info.set_smdp_address(*smdp_activation_code);
} else if (smds_activation_code && !smds_activation_code->empty()) {
esim_profile_info.set_smds_address(*smds_activation_code);
} else {
NET_LOG(ERROR) << "Failed to find an activation code when constructing "
"EUICC upload request";
continue;
}
mutable_esim_profiles->Add(std::move(esim_profile_info));
}
upload_request->set_clear_profile_list(clear_profile_list);
return upload_request;
}
void EuiccStatusUploader::OnManagedNetworkConfigurationHandlerShuttingDown() {
if (managed_network_configuration_handler_ &&
managed_network_configuration_handler_->HasObserver(this)) {
managed_network_configuration_handler_->RemoveObserver(this);
}
managed_network_configuration_handler_ = nullptr;
}
void EuiccStatusUploader::OnRegistrationStateChanged(
CloudPolicyClient* client) {
MaybeUploadStatus();
}
void EuiccStatusUploader::OnPolicyFetched(CloudPolicyClient* client) {
if (is_policy_fetched_) {
return;
}
is_policy_fetched_ = true;
MaybeUploadStatus();
}
void EuiccStatusUploader::PoliciesApplied(const std::string& userhash) {
MaybeUploadStatus();
}
void EuiccStatusUploader::OnManagedCellularPrefChanged() {
MaybeUploadStatus();
}
void EuiccStatusUploader::OnAvailableEuiccListChanged() {
MaybeUploadStatus();
}
void EuiccStatusUploader::OnEuiccReset(const dbus::ObjectPath& euicc_path) {
local_state_->SetBoolean(kShouldSendClearProfilesRequestPref, true);
MaybeUploadStatus();
}
base::Value::Dict EuiccStatusUploader::GetCurrentEuiccStatus() const {
auto status = base::Value::Dict().Set(
kLastUploadedEuiccStatusEuiccCountKey,
static_cast<int>(
ash::HermesManagerClient::Get()->GetAvailableEuiccs().size()));
base::Value::List esim_profiles;
for (const auto& esim_profile : ash::NetworkHandler::Get()
->cellular_esim_profile_handler()
->GetESimProfiles()) {
if (esim_profile.iccid().empty()) {
continue;
}
const base::Value::Dict* esim_metadata =
ash::NetworkHandler::Get()
->managed_cellular_pref_handler()
->GetESimMetadata(esim_profile.iccid());
if (!esim_metadata) {
continue;
}
base::Value::Dict esim_profile_to_add;
esim_profile_to_add.Set(kLastUploadedEuiccStatusESimProfilesIccidKey,
esim_profile.iccid());
const std::string* const smdp_activation_code =
esim_metadata->FindString(::onc::cellular::kSMDPAddress);
const std::string* const smds_activation_code =
esim_metadata->FindString(::onc::cellular::kSMDSAddress);
if (smdp_activation_code && !smdp_activation_code->empty()) {
esim_profile_to_add.Set(
kLastUploadedEuiccStatusESimProfilesSmdpActivationCodeKey,
*smdp_activation_code);
} else if (smds_activation_code && !smds_activation_code->empty()) {
esim_profile_to_add.Set(
kLastUploadedEuiccStatusESimProfilesSmdsActivationCodeKey,
*smds_activation_code);
} else {
NET_LOG(ERROR) << "Failed to find an SM-DP+ or SM-DS activation code "
<< "in the eSIM metadata, skipping entry";
continue;
}
const std::string* network_name =
esim_metadata->FindString(::onc::network_config::kName);
if (network_name && !network_name->empty()) {
esim_profile_to_add.Set(
kLastUploadedEuiccStatusESimProfilesNetworkNameKey, *network_name);
}
esim_profiles.Append(std::move(esim_profile_to_add));
}
status.SetByDottedPath(kLastUploadedEuiccStatusESimProfilesKey,
std::move(esim_profiles));
return status;
}
void EuiccStatusUploader::MaybeUploadStatus() {
if (!client_->is_registered()) {
VLOG(1) << "Policy client is not registered.";
return;
}
if (!is_policy_fetched_) {
VLOG(1) << "Policy not fetched yet.";
return;
}
if (!is_device_managed_callback_.Run()) {
VLOG(1) << "Device is unmanaged or deprovisioned.";
return;
}
if (!managed_network_configuration_handler_) {
LOG(WARNING) << "ManageNetworkConfigurationHandler is not initialized.";
return;
}
if (ash::HermesManagerClient::Get()->GetAvailableEuiccs().empty()) {
VLOG(1) << "No EUICC available on the device.";
return;
}
const base::Value::Dict& last_uploaded_pref =
local_state_->GetDict(kLastUploadedEuiccStatusPref);
auto current_state = GetCurrentEuiccStatus();
if (local_state_->GetBoolean(kShouldSendClearProfilesRequestPref)) {
UploadStatus(std::move(current_state));
return;
}
if (attempted_upload_status_ == current_state) {
if (!retry_timer_) {
retry_timer_ = std::make_unique<base::OneShotTimer>();
retry_timer_->Start(FROM_HERE, retry_entry_.GetTimeUntilRelease(),
base::BindOnce(&EuiccStatusUploader::RetryUpload,
weak_ptr_factory_.GetWeakPtr()));
}
return;
}
retry_timer_.reset();
if (last_uploaded_pref != current_state) {
UploadStatus(std::move(current_state));
}
}
void EuiccStatusUploader::UploadStatus(base::Value::Dict status) {
if (currently_uploading_) {
return;
}
currently_uploading_ = true;
attempted_upload_status_ = std::move(status);
const bool should_send_clear_profiles_request =
local_state_->GetBoolean(kShouldSendClearProfilesRequestPref);
auto upload_request = ConstructRequestFromStatus(
attempted_upload_status_, should_send_clear_profiles_request);
client_->UploadEuiccInfo(
std::move(upload_request),
base::BindOnce(&EuiccStatusUploader::OnStatusUploaded,
weak_ptr_factory_.GetWeakPtr(),
should_send_clear_profiles_request));
}
void EuiccStatusUploader::OnStatusUploaded(
bool should_send_clear_profiles_request,
bool success) {
currently_uploading_ = false;
retry_entry_.InformOfRequest(success);
base::UmaHistogramBoolean(
"Network.Cellular.ESim.Policy.EuiccStatusUploadResult", success);
if (!success) {
LOG(ERROR) << "Failed to upload EUICC status.";
MaybeUploadStatus();
return;
}
VLOG(1) << "EUICC status successfully uploaded.";
local_state_->SetDict(kLastUploadedEuiccStatusPref,
std::move(attempted_upload_status_));
if (should_send_clear_profiles_request) {
local_state_->ClearPref(kShouldSendClearProfilesRequestPref);
}
attempted_upload_status_.clear();
MaybeUploadStatus();
}
void EuiccStatusUploader::RetryUpload() {
attempted_upload_status_.clear();
MaybeUploadStatus();
}
void EuiccStatusUploader::FireRetryTimerIfExistsForTesting() {
if (retry_timer_) {
retry_timer_->FireNow();
}
}
}