| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/dbus/chrome_features_service_provider.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/field_trial.h" |
| #include "chrome/browser/ash/bruschetta/bruschetta_util.h" |
| #include "chrome/browser/ash/crostini/crostini_features.h" |
| #include "chrome/browser/ash/crostini/crostini_pref_names.h" |
| #include "chrome/browser/ash/plugin_vm/plugin_vm_features.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chromeos/ash/components/install_attributes/install_attributes.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #include "chromeos/ash/components/settings/cros_settings_names.h" |
| #include "chromeos/ash/experiences/arc/arc_features.h" |
| #include "components/prefs/pref_service.h" |
| #include "dbus/bus.h" |
| #include "dbus/message.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // A prefix to apply to all features which Chrome OS platform-side code wishes |
| // to check. |
| // This prefix must *only* be applied to features on the platform side, and no |
| // |base::Feature|s should be defined with this prefix. |
| // A presubmit will enforce that no |base::Feature|s will be defined with this |
| // prefix. |
| // TODO(crbug.com/40202807): Add the aforementioned presubmit. |
| constexpr char kCrOSLateBootFeaturePrefix[] = "CrOSLateBoot"; |
| |
| void SendResponse(dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender, |
| bool answer, |
| const std::string& reason = std::string()) { |
| std::unique_ptr<dbus::Response> response = |
| dbus::Response::FromMethodCall(method_call); |
| dbus::MessageWriter writer(response.get()); |
| writer.AppendBool(answer); |
| if (!reason.empty()) |
| writer.AppendString(reason); |
| std::move(response_sender).Run(std::move(response)); |
| } |
| |
| Profile* GetSenderProfile( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender* response_sender) { |
| dbus::MessageReader reader(method_call); |
| std::string user_id_hash; |
| |
| if (!reader.PopString(&user_id_hash)) { |
| LOG(ERROR) << "Failed to pop user_id_hash from incoming message."; |
| std::move(*response_sender) |
| .Run(dbus::ErrorResponse::FromMethodCall(method_call, |
| DBUS_ERROR_INVALID_ARGS, |
| "No user_id_hash string arg")); |
| return nullptr; |
| } |
| |
| if (user_id_hash.empty()) |
| return ProfileManager::GetActiveUserProfile(); |
| |
| return g_browser_process->profile_manager()->GetProfileByPath( |
| ProfileHelper::GetProfilePathByUserIdHash(user_id_hash)); |
| } |
| |
| } // namespace |
| |
| ChromeFeaturesServiceProvider::ChromeFeaturesServiceProvider( |
| std::unique_ptr<base::FeatureList::Accessor> feature_list_accessor) |
| : feature_list_accessor_(std::move(feature_list_accessor)) {} |
| |
| ChromeFeaturesServiceProvider::~ChromeFeaturesServiceProvider() = default; |
| |
| void ChromeFeaturesServiceProvider::Start( |
| scoped_refptr<dbus::ExportedObject> exported_object) { |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsFeatureEnabledMethod, |
| base::BindRepeating(&ChromeFeaturesServiceProvider::IsFeatureEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceGetFeatureParamsMethod, |
| base::BindRepeating(&ChromeFeaturesServiceProvider::GetFeatureParams, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsBruschettaEnabledMethod, |
| base::BindRepeating(&ChromeFeaturesServiceProvider::IsBruschettaEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsCrostiniEnabledMethod, |
| base::BindRepeating(&ChromeFeaturesServiceProvider::IsCrostiniEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsPluginVmEnabledMethod, |
| base::BindRepeating(&ChromeFeaturesServiceProvider::IsPluginVmEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsCryptohomeDistributedModelEnabledMethod, |
| base::BindRepeating( |
| &ChromeFeaturesServiceProvider::IsCryptohomeDistributedModelEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsCryptohomeUserDataAuthEnabledMethod, |
| base::BindRepeating( |
| &ChromeFeaturesServiceProvider::IsCryptohomeUserDataAuthEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos:: |
| kChromeFeaturesServiceIsCryptohomeUserDataAuthKillswitchEnabledMethod, |
| base::BindRepeating(&ChromeFeaturesServiceProvider:: |
| IsCryptohomeUserDataAuthKillswitchEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsVmManagementCliAllowedMethod, |
| base::BindRepeating( |
| &ChromeFeaturesServiceProvider::IsVmManagementCliAllowed, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsPeripheralDataAccessEnabledMethod, |
| base::BindRepeating( |
| &ChromeFeaturesServiceProvider::IsPeripheralDataAccessEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsDNSProxyEnabledMethod, |
| base::BindRepeating(&ChromeFeaturesServiceProvider::IsDnsProxyEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| exported_object->ExportMethod( |
| chromeos::kChromeFeaturesServiceInterface, |
| chromeos::kChromeFeaturesServiceIsRootNsDnsProxyEnabledMethod, |
| base::BindRepeating( |
| &ChromeFeaturesServiceProvider::IsRootNsDnsProxyEnabled, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating(&ChromeFeaturesServiceProvider::OnExported, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ChromeFeaturesServiceProvider::OnExported( |
| const std::string& interface_name, |
| const std::string& method_name, |
| bool success) { |
| if (!success) { |
| LOG(ERROR) << "Failed to export " << interface_name << "." << method_name; |
| } |
| } |
| |
| void ChromeFeaturesServiceProvider::IsFeatureEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| static const base::Feature constexpr* kFeatureLookup[] = { |
| &arc::kBootCompletedBroadcastFeature, |
| &arc::kCustomTabsExperimentFeature, |
| &arc::kNativeBridgeToggleFeature, |
| &features::kSessionManagerLongKillTimeout, |
| &features::kSessionManagerLivenessCheck, |
| &features::kBorealisProvision, |
| |
| }; |
| |
| dbus::MessageReader reader(method_call); |
| std::string feature_name; |
| if (!reader.PopString(&feature_name)) { |
| LOG(ERROR) << "Failed to pop feature_name from incoming message."; |
| std::move(response_sender) |
| .Run(dbus::ErrorResponse::FromMethodCall( |
| method_call, DBUS_ERROR_INVALID_ARGS, |
| "Missing or invalid feature_name string arg.")); |
| return; |
| } |
| |
| auto* const* it = |
| std::ranges::find(kFeatureLookup, feature_name, &base::Feature::name); |
| if (it != std::end(kFeatureLookup)) { |
| SendResponse(method_call, std::move(response_sender), |
| base::FeatureList::IsEnabled(**it)); |
| return; |
| } |
| // Not on our list. Potentially look up by name instead. |
| // Only search for arbitrary trial names that begin with the appropriate |
| // prefix, since looking up a feature by name will not be able to get the |
| // default value associated with any `base::Feature` defined in the code |
| // base. |
| // Separately, a presubmit will enforce that no `base::Feature` definition |
| // has a name starting with this prefix. |
| // TODO(crbug.com/40202807): Add the aforementioned presubmit. |
| base::FeatureList::OverrideState state = |
| base::FeatureList::OVERRIDE_USE_DEFAULT; |
| if (feature_name.find(kCrOSLateBootFeaturePrefix) == 0) { |
| state = feature_list_accessor_->GetOverrideStateByFeatureName(feature_name); |
| } else { |
| LOG(ERROR) << "Invalid prefix on feature " << feature_name << " (want " |
| << kCrOSLateBootFeaturePrefix << ")"; |
| std::move(response_sender) |
| .Run(dbus::ErrorResponse::FromMethodCall( |
| method_call, DBUS_ERROR_INVALID_ARGS, |
| base::StrCat({"Invalid prefix for feature name: '", feature_name, |
| "'. Want ", kCrOSLateBootFeaturePrefix}))); |
| return; |
| } |
| if (state == base::FeatureList::OVERRIDE_USE_DEFAULT) { |
| VLOG(1) << "Unexpected feature name '" << feature_name << "'" |
| << " (likely just indicates there isn't a variations seed)."; |
| // This isn't really an error, we're just using the error channel to signal |
| // to feature_library that it should fall back to its defaults. |
| std::move(response_sender) |
| .Run(dbus::ErrorResponse::FromMethodCall( |
| method_call, DBUS_ERROR_INVALID_ARGS, |
| base::StrCat({"Chrome can't get state for '", feature_name, |
| "'; feature_library will decide"}))); |
| return; |
| } |
| SendResponse(method_call, std::move(response_sender), |
| state == base::FeatureList::OVERRIDE_ENABLE_FEATURE); |
| } |
| |
| void ChromeFeaturesServiceProvider::GetFeatureParams( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| dbus::MessageReader reader(method_call); |
| dbus::MessageReader array_reader(nullptr); |
| if (!reader.PopArray(&array_reader)) { |
| LOG(ERROR) << "Failed to read array of feature names."; |
| std::move(response_sender) |
| .Run(dbus::ErrorResponse::FromMethodCall( |
| method_call, DBUS_ERROR_INVALID_ARGS, |
| "Could not pop string array of feature names")); |
| return; |
| } |
| |
| std::vector<std::string> features; |
| std::map<std::string, std::map<std::string, std::string>> params_map; |
| std::map<std::string, bool> enabled_map; |
| while (array_reader.HasMoreData()) { |
| std::string feature_name; |
| |
| if (!array_reader.PopString(&feature_name)) { |
| LOG(ERROR) << "Failed to pop feature_name from array."; |
| std::move(response_sender) |
| .Run(dbus::ErrorResponse::FromMethodCall( |
| method_call, DBUS_ERROR_INVALID_ARGS, |
| "Missing or invalid feature_name string arg in array.")); |
| return; |
| } |
| |
| if (feature_name.find(kCrOSLateBootFeaturePrefix) != 0) { |
| LOG(ERROR) << "Unexpected prefix on feature name '" << feature_name << "'" |
| << " (want " << kCrOSLateBootFeaturePrefix << ")"; |
| std::move(response_sender) |
| .Run(dbus::ErrorResponse::FromMethodCall( |
| method_call, DBUS_ERROR_INVALID_ARGS, |
| base::StrCat({"Invalid prefix for feature name: '", feature_name, |
| "'. Want ", kCrOSLateBootFeaturePrefix}))); |
| return; |
| } |
| |
| features.push_back(feature_name); |
| |
| base::FeatureList::OverrideState state = |
| feature_list_accessor_->GetOverrideStateByFeatureName(feature_name); |
| if (state == base::FeatureList::OVERRIDE_ENABLE_FEATURE) { |
| enabled_map[feature_name] = true; |
| } else if (state == base::FeatureList::OVERRIDE_DISABLE_FEATURE) { |
| enabled_map[feature_name] = false; |
| } |
| // else leave it out of the map. |
| |
| std::map<std::string, std::string> per_feature_map; |
| if (!feature_list_accessor_->GetParamsByFeatureName(feature_name, |
| &per_feature_map)) { |
| VLOG(1) << "No trial found for '" << feature_name << "', skipping." |
| << " (likely just means there is no variations seed)"; |
| continue; |
| } |
| params_map[feature_name] = std::move(per_feature_map); |
| } |
| |
| // Build response |
| std::unique_ptr<dbus::Response> response = |
| dbus::Response::FromMethodCall(method_call); |
| dbus::MessageWriter writer(response.get()); |
| dbus::MessageWriter array_writer(nullptr); |
| // A map from feature name to: |
| // * two booleans: |
| // * Whether to use the override (or the default), |
| // * What the override state is (only valid if we should use the |
| // override value). |
| // * Another map, from parameter name to value. |
| writer.OpenArray("{s(bba{ss})}", &array_writer); |
| for (const auto& feature_name : features) { |
| dbus::MessageWriter feature_dict_writer(nullptr); |
| array_writer.OpenDictEntry(&feature_dict_writer); |
| feature_dict_writer.AppendString(feature_name); |
| dbus::MessageWriter struct_writer(nullptr); |
| feature_dict_writer.OpenStruct(&struct_writer); |
| |
| if (enabled_map.find(feature_name) != enabled_map.end()) { |
| struct_writer.AppendBool(true); // Use override |
| struct_writer.AppendBool(enabled_map[feature_name]); |
| } else { |
| struct_writer.AppendBool(false); // Ignore override |
| struct_writer.AppendBool(false); // Arbitrary choice |
| } |
| |
| dbus::MessageWriter sub_array_writer(nullptr); |
| struct_writer.OpenArray("{ss}", &sub_array_writer); |
| if (params_map.find(feature_name) != params_map.end()) { |
| const auto& submap = params_map[feature_name]; |
| for (const auto& [key, value] : submap) { |
| dbus::MessageWriter dict_writer(nullptr); |
| sub_array_writer.OpenDictEntry(&dict_writer); |
| dict_writer.AppendString(key); |
| dict_writer.AppendString(value); |
| sub_array_writer.CloseContainer(&dict_writer); |
| } |
| } |
| struct_writer.CloseContainer(&sub_array_writer); |
| feature_dict_writer.CloseContainer(&struct_writer); |
| array_writer.CloseContainer(&feature_dict_writer); |
| } |
| writer.CloseContainer(&array_writer); |
| std::move(response_sender).Run(std::move(response)); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsBruschettaEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| Profile* profile = GetSenderProfile(method_call, &response_sender); |
| if (!profile) { |
| return; |
| } |
| |
| bool answer = !bruschetta::GetInstallableConfigs(profile).empty(); |
| SendResponse(method_call, std::move(response_sender), answer); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsCrostiniEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| Profile* profile = GetSenderProfile(method_call, &response_sender); |
| if (!profile) |
| return; |
| |
| std::string reason; |
| bool answer = |
| crostini::CrostiniFeatures::Get()->IsAllowedNow(profile, &reason); |
| SendResponse(method_call, std::move(response_sender), answer, reason); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsCryptohomeDistributedModelEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| SendResponse( |
| method_call, std::move(response_sender), |
| base::FeatureList::IsEnabled(::features::kCryptohomeDistributedModel)); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsCryptohomeUserDataAuthEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| SendResponse( |
| method_call, std::move(response_sender), |
| base::FeatureList::IsEnabled(::features::kCryptohomeUserDataAuth)); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsCryptohomeUserDataAuthKillswitchEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| SendResponse(method_call, std::move(response_sender), |
| base::FeatureList::IsEnabled( |
| ::features::kCryptohomeUserDataAuthKillswitch)); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsPluginVmEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| Profile* profile = GetSenderProfile(method_call, &response_sender); |
| if (!profile) |
| return; |
| |
| std::string reason; |
| bool answer = plugin_vm::PluginVmFeatures::Get()->IsAllowed(profile, &reason); |
| SendResponse(method_call, std::move(response_sender), answer, reason); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsVmManagementCliAllowed( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| Profile* profile = GetSenderProfile(method_call, &response_sender); |
| if (!profile) |
| return; |
| |
| SendResponse(method_call, std::move(response_sender), |
| profile->GetPrefs()->GetBoolean( |
| crostini::prefs::kVmManagementCliAllowedByPolicy)); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsPeripheralDataAccessEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| // TODO(1242686): Add end-to-end tests for this D-Bus signal. |
| |
| bool peripheral_data_access_enabled = false; |
| // Enterprise managed devices use the local state pref. |
| if (InstallAttributes::Get()->IsEnterpriseManaged()) { |
| peripheral_data_access_enabled = |
| g_browser_process->local_state()->GetBoolean( |
| prefs::kLocalStateDevicePeripheralDataAccessEnabled); |
| } else { |
| // Consumer devices use the CrosSetting pref. |
| CrosSettings::Get()->GetBoolean(kDevicePeripheralDataAccessEnabled, |
| &peripheral_data_access_enabled); |
| } |
| SendResponse(method_call, std::move(response_sender), |
| peripheral_data_access_enabled); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsDnsProxyEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| SendResponse(method_call, std::move(response_sender), |
| !base::FeatureList::IsEnabled(features::kDisableDnsProxy)); |
| } |
| |
| void ChromeFeaturesServiceProvider::IsRootNsDnsProxyEnabled( |
| dbus::MethodCall* method_call, |
| dbus::ExportedObject::ResponseSender response_sender) { |
| SendResponse(method_call, std::move(response_sender), |
| base::FeatureList::IsEnabled(features::kEnableRootNsDnsProxy)); |
| } |
| |
| } // namespace ash |