#include "chrome/browser/ash/cryptauth/client_app_metadata_provider_service.h"
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/check_deref.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/linux_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/version.h"
#include "chrome/browser/ash/cryptauth/cryptauth_device_id_provider.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_better_together_feature_metadata.pb.h"
#include "chromeos/ash/services/device_sync/public/cpp/gcm_constants.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/gcm_driver/instance_id/instance_id_profile_service.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
namespace ash {
namespace {
const char kInstanceIdScope[] = "GCM";
const char kDefaultModelName[] = "Chromebook";
const cryptauthv2::FeatureMetadata& GenerateFeatureMetadata() {
static const base::NoDestructor<cryptauthv2::FeatureMetadata>
feature_metadata([] {
cryptauthv2::BetterTogetherFeatureMetadata inner_metadata;
inner_metadata.add_supported_features(
cryptauthv2::
BetterTogetherFeatureMetadata_FeatureName_EASY_UNLOCK_CLIENT);
inner_metadata.add_supported_features(
cryptauthv2::
BetterTogetherFeatureMetadata_FeatureName_BETTER_TOGETHER_CLIENT);
if (base::FeatureList::IsEnabled(features::kInstantTethering)) {
inner_metadata.add_supported_features(
cryptauthv2::
BetterTogetherFeatureMetadata_FeatureName_MAGIC_TETHER_CLIENT);
}
if (features::IsPhoneHubEnabled()) {
inner_metadata.add_supported_features(
cryptauthv2::
BetterTogetherFeatureMetadata_FeatureName_PHONE_HUB_CLIENT);
}
if (features::IsWifiSyncAndroidEnabled()) {
inner_metadata.add_supported_features(
cryptauthv2::
BetterTogetherFeatureMetadata_FeatureName_WIFI_SYNC_CLIENT);
}
if (features::IsEcheSWAEnabled()) {
inner_metadata.add_supported_features(
cryptauthv2::
BetterTogetherFeatureMetadata_FeatureName_ECHE_CLIENT);
}
if (features::IsPhoneHubCameraRollEnabled()) {
inner_metadata.add_supported_features(
cryptauthv2::
BetterTogetherFeatureMetadata_FeatureName_PHONE_HUB_CAMERA_ROLL_CLIENT);
}
cryptauthv2::FeatureMetadata feature_metadata;
feature_metadata.set_feature_type(
cryptauthv2::FeatureMetadata_Feature_BETTER_TOGETHER);
feature_metadata.set_metadata(inner_metadata.SerializeAsString());
return feature_metadata;
}());
return *feature_metadata;
}
cryptauthv2::ApplicationSpecificMetadata GenerateApplicationSpecificMetadata(
const std::string& gcm_registration_id,
int64_t software_version_code) {
cryptauthv2::ApplicationSpecificMetadata metadata;
metadata.set_gcm_registration_id(gcm_registration_id);
metadata.set_notification_enabled(true);
metadata.set_device_software_version(base::GetLinuxDistro());
metadata.set_device_software_version_code(software_version_code);
metadata.set_device_software_package(device_sync::kCryptAuthGcmAppId);
return metadata;
}
void LogInstanceIdTokenFetchRetries(int count) {
base::UmaHistogramExactLinear(
"CryptAuth.ClientAppMetadataInstanceIdTokenFetch.Retries", count, 2);
}
}
void ClientAppMetadataProviderService::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterStringPref(::prefs::kCryptAuthInstanceId, std::string());
registry->RegisterStringPref(::prefs::kCryptAuthInstanceIdToken,
std::string());
}
int64_t ClientAppMetadataProviderService::ConvertVersionCodeToInt64(
const std::string& version_code_str) {
static const size_t kNumDigitsInLastSection = 3;
std::string version_code_copy = version_code_str;
size_t last_period_index = version_code_copy.rfind('.');
if (last_period_index != std::string::npos) {
size_t num_digits_to_add = kNumDigitsInLastSection + last_period_index -
(version_code_copy.size() - 1u);
version_code_copy.insert(last_period_index + 1u ,
std::string(num_digits_to_add, '0') );
}
int64_t code = 0;
for (auto it = version_code_copy.cbegin(); it != version_code_copy.cend();
++it) {
if (*it < '0' || *it > '9')
continue;
code = code * 10 + (*it - '0');
}
return code;
}
ClientAppMetadataProviderService::ClientAppMetadataProviderService(
PrefService* local_state,
PrefService* profile_pref_service,
NetworkStateHandler* network_state_handler,
instance_id::InstanceIDProfileService* instance_id_profile_service)
: local_state_(CHECK_DEREF(local_state)),
pref_service_(profile_pref_service),
network_state_handler_(network_state_handler),
instance_id_profile_service_(instance_id_profile_service) {}
ClientAppMetadataProviderService::~ClientAppMetadataProviderService() {
InvokePendingCallbacks();
}
void ClientAppMetadataProviderService::GetClientAppMetadata(
const std::string& gcm_registration_id,
GetMetadataCallback callback) {
pending_callbacks_.push_back(std::move(callback));
if (client_app_metadata_) {
DCHECK_EQ(1, client_app_metadata_->application_specific_metadata_size());
client_app_metadata_->mutable_application_specific_metadata(0 )
->set_gcm_registration_id(gcm_registration_id);
InvokePendingCallbacks();
return;
}
if (!instance_id_profile_service_) {
InvokePendingCallbacks();
return;
}
bool was_already_in_progress = pending_gcm_registration_id_.has_value();
pending_gcm_registration_id_ = gcm_registration_id;
if (was_already_in_progress)
return;
device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
&ClientAppMetadataProviderService::OnBluetoothAdapterFetched,
weak_ptr_factory_.GetWeakPtr()));
}
void ClientAppMetadataProviderService::Shutdown() {
instance_id_profile_service_ = nullptr;
weak_ptr_factory_.InvalidateWeakPtrs();
pending_gcm_registration_id_.reset();
InvokePendingCallbacks();
}
void ClientAppMetadataProviderService::OnBluetoothAdapterFetched(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter) {
base::SysInfo::GetHardwareInfo(
base::BindOnce(&ClientAppMetadataProviderService::OnHardwareInfoFetched,
weak_ptr_factory_.GetWeakPtr(), bluetooth_adapter));
}
void ClientAppMetadataProviderService::OnHardwareInfoFetched(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
base::SysInfo::HardwareInfo hardware_info) {
GetInstanceId()->GetID(base::BindOnce(
&ClientAppMetadataProviderService::OnInstanceIdFetched,
weak_ptr_factory_.GetWeakPtr(), bluetooth_adapter, hardware_info));
}
void ClientAppMetadataProviderService::OnInstanceIdFetched(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
const base::SysInfo::HardwareInfo& hardware_info,
const std::string& instance_id) {
DCHECK(!instance_id.empty());
std::string previous_instance_id =
pref_service_->GetString(::prefs::kCryptAuthInstanceId);
if (!previous_instance_id.empty()) {
base::UmaHistogramBoolean("CryptAuth.InstanceId.DidInstanceIdChange",
previous_instance_id != instance_id);
}
pref_service_->SetString(::prefs::kCryptAuthInstanceId, instance_id);
GetInstanceId()->GetToken(
device_sync::
kCryptAuthV2EnrollmentAuthorizedEntity ,
kInstanceIdScope , base::TimeDelta() ,
{} ,
base::BindOnce(
&ClientAppMetadataProviderService::OnInstanceIdTokenFetched,
weak_ptr_factory_.GetWeakPtr(), bluetooth_adapter, hardware_info,
instance_id));
}
void ClientAppMetadataProviderService::OnInstanceIdTokenFetched(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
const base::SysInfo::HardwareInfo& hardware_info,
const std::string& instance_id,
const std::string& token,
instance_id::InstanceID::Result result) {
if (token.find(':') != std::string::npos &&
!base::StartsWith(token, instance_id,
base::CompareCase::INSENSITIVE_ASCII)) {
GetInstanceId()->DeleteID(base::BindOnce(
&ClientAppMetadataProviderService::OnInstanceIdDeleted,
weak_ptr_factory_.GetWeakPtr(), bluetooth_adapter, hardware_info));
return;
}
LogInstanceIdTokenFetchRetries(instance_id_recreated_ ? 1 : 0);
std::string gcm_registration_id = *pending_gcm_registration_id_;
pending_gcm_registration_id_.reset();
instance_id_recreated_ = false;
UMA_HISTOGRAM_ENUMERATION(
"CryptAuth.ClientAppMetadataInstanceIdTokenFetch.Result", result);
if (result != instance_id::InstanceID::Result::SUCCESS) {
PA_LOG(WARNING) << "ClientAppMetadataProviderService::"
<< "OnInstanceIdTokenFetched(): Failed to fetch InstanceID "
<< "token; result: " << result << ".";
InvokePendingCallbacks();
return;
}
DCHECK(!token.empty());
std::string previous_instance_id_token =
pref_service_->GetString(::prefs::kCryptAuthInstanceIdToken);
if (!previous_instance_id_token.empty()) {
base::UmaHistogramBoolean("CryptAuth.InstanceId.DidInstanceIdTokenChange",
previous_instance_id_token != token);
}
pref_service_->SetString(::prefs::kCryptAuthInstanceIdToken, token);
cryptauthv2::ClientAppMetadata metadata;
metadata.add_application_specific_metadata()->CopyFrom(
GenerateApplicationSpecificMetadata(gcm_registration_id,
SoftwareVersionCodeAsInt64()));
metadata.set_instance_id(instance_id);
metadata.set_instance_id_token(token);
metadata.set_long_device_id(
cryptauth_device_id::GetDeviceID(local_state_.get()));
metadata.set_locale(g_browser_process->GetApplicationLocale());
metadata.set_device_os_version(base::GetLinuxDistro());
metadata.set_device_os_version_code(SoftwareVersionCodeAsInt64());
metadata.set_device_os_release(std::string(version_info::GetVersionNumber()));
metadata.set_device_os_codename(std::string(version_info::GetProductName()));
metadata.set_device_display_diagonal_mils(0);
base::UmaHistogramBoolean("CryptAuth.ClientAppMetadata.IsModelEmpty",
hardware_info.model.empty());
metadata.set_device_model(hardware_info.model.empty() ? kDefaultModelName
: hardware_info.model);
base::UmaHistogramBoolean("CryptAuth.ClientAppMetadata.IsManufacturerEmpty",
hardware_info.manufacturer.empty());
metadata.set_device_manufacturer(hardware_info.manufacturer);
metadata.set_device_type(cryptauthv2::ClientAppMetadata_DeviceType_CHROME);
metadata.set_using_secure_screenlock(
pref_service_->GetBoolean(prefs::kEnableAutoScreenLock));
metadata.set_auto_unlock_screenlock_supported(false);
metadata.set_auto_unlock_screenlock_enabled(false);
metadata.set_bluetooth_radio_supported(bluetooth_adapter->IsPresent());
metadata.set_bluetooth_radio_enabled(bluetooth_adapter->IsPowered());
metadata.set_ble_radio_supported(bluetooth_adapter->IsPresent());
metadata.set_mobile_data_supported(
network_state_handler_->IsTechnologyAvailable(
NetworkTypePattern::Cellular()));
metadata.set_tethering_supported(false);
metadata.add_feature_metadata()->CopyFrom(GenerateFeatureMetadata());
metadata.set_pixel_experience(false);
metadata.set_arc_plus_plus(false);
metadata.set_hardware_user_presence_supported(false);
metadata.set_user_verification_supported(true);
metadata.set_trusted_execution_environment_supported(false);
metadata.set_dedicated_secure_element_supported(false);
client_app_metadata_ = metadata;
InvokePendingCallbacks();
}
void ClientAppMetadataProviderService::OnInstanceIdDeleted(
scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
const base::SysInfo::HardwareInfo& hardware_info,
instance_id::InstanceID::Result result) {
instance_id_profile_service_->driver()->RemoveInstanceID(
device_sync::kCryptAuthGcmAppId);
if (instance_id_recreated_) {
LogInstanceIdTokenFetchRetries(2);
PA_LOG(WARNING) << "ClientAppMetadataProviderService::"
<< "OnInstanceIdDeleted(): Instance Id deleted twice in a "
<< "row, aborting; result: " << result << ".";
pending_gcm_registration_id_.reset();
instance_id_recreated_ = false;
InvokePendingCallbacks();
return;
}
instance_id_recreated_ = true;
OnHardwareInfoFetched(bluetooth_adapter, hardware_info);
}
instance_id::InstanceID* ClientAppMetadataProviderService::GetInstanceId() {
DCHECK(instance_id_profile_service_);
DCHECK(instance_id_profile_service_->driver());
return instance_id_profile_service_->driver()->GetInstanceID(
device_sync::kCryptAuthGcmAppId);
}
int64_t ClientAppMetadataProviderService::SoftwareVersionCodeAsInt64() {
static const int64_t version_code =
ConvertVersionCodeToInt64(std::string(version_info::GetVersionNumber()));
return version_code;
}
void ClientAppMetadataProviderService::InvokePendingCallbacks() {
auto it = pending_callbacks_.begin();
while (it != pending_callbacks_.end()) {
std::move(*it).Run(client_app_metadata_);
it = pending_callbacks_.erase(it);
}
}
}