// Copyright 2013 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/settings/about_flags.h"
#include <string_view>
#include "base/check.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/values.h"
#include "chrome/browser/about_flags.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/login/session/user_session_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/site_isolation/about_flags.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/standalone_browser/lacros_availability.h"
#include "components/account_id/account_id.h"
#include "components/flags_ui/flags_storage.h"
#include "components/flags_ui/flags_ui_pref_names.h"
#include "components/ownership/owner_settings_service.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "third_party/cros_system_api/switches/chrome_switches.h"
namespace ash {
namespace about_flags {
namespace {
std::set<std::string> ParseFlagsFromCommandLine(
base::CommandLine* command_line) {
std::set<std::string> flags;
std::string encoded =
command_line->GetSwitchValueNative(chromeos::switches::kFeatureFlags);
if (encoded.empty()) {
return flags;
}
auto flags_list = base::JSONReader::Read(encoded);
if (!flags_list) {
LOG(WARNING) << "Failed to parse feature flags configuration";
return flags;
}
for (const auto& flag : flags_list.value().GetList()) {
if (!flag.is_string()) {
LOG(WARNING) << "Invalid entry in encoded feature flags";
continue;
}
flags.insert(flag.GetString());
}
return flags;
}
std::map<std::string, std::string> ParseOriginListFlagsFromCommmandLine(
base::CommandLine* command_line) {
std::map<std::string, std::string> origin_list_flags;
std::string encoded = command_line->GetSwitchValueNative(
chromeos::switches::kFeatureFlagsOriginList);
if (encoded.empty()) {
return origin_list_flags;
}
auto origin_list_flags_dict = base::JSONReader::Read(encoded);
if (!origin_list_flags_dict) {
LOG(WARNING) << "Failed to parse origin list configuration";
return origin_list_flags;
}
for (const auto entry : origin_list_flags_dict->GetDict()) {
if (!entry.second.is_string()) {
LOG(WARNING) << "Invalid entry in encoded origin list flags";
continue;
}
origin_list_flags[entry.first] = entry.second.GetString();
}
return origin_list_flags;
}
} // namespace
OwnerFlagsStorage::OwnerFlagsStorage(
PrefService* prefs,
ownership::OwnerSettingsService* owner_settings_service)
: flags_ui::PrefServiceFlagsStorage(prefs),
owner_settings_service_(owner_settings_service) {}
OwnerFlagsStorage::~OwnerFlagsStorage() {}
bool OwnerFlagsStorage::SetFlags(const std::set<std::string>& flags) {
// Write the flags configuration to profile preferences, which are used to
// determine flags to apply when launching a user session.
PrefServiceFlagsStorage::SetFlags(flags);
// Also write the flags to device settings so they get applied to the Chrome
// OS login screen. The device setting is read by session_manager and passed
// to Chrome via a command line flag on startup.
base::Value::List feature_flags_list;
for (const auto& flag : flags) {
feature_flags_list.Append(flag);
}
owner_settings_service_->Set(kFeatureFlags,
base::Value(std::move(feature_flags_list)));
return true;
}
ReadOnlyFlagsStorage::ReadOnlyFlagsStorage(
const std::set<std::string>& flags,
const std::map<std::string, std::string>& origin_list_flags)
: flags_(flags), origin_list_flags_(origin_list_flags) {}
ReadOnlyFlagsStorage::ReadOnlyFlagsStorage(base::CommandLine* command_line)
: flags_(ParseFlagsFromCommandLine(command_line)),
origin_list_flags_(ParseOriginListFlagsFromCommmandLine(command_line)) {}
ReadOnlyFlagsStorage::~ReadOnlyFlagsStorage() = default;
std::set<std::string> ReadOnlyFlagsStorage::GetFlags() const {
return flags_;
}
bool ReadOnlyFlagsStorage::SetFlags(const std::set<std::string>& flags) {
return false;
}
void ReadOnlyFlagsStorage::CommitPendingWrites() {}
std::string ReadOnlyFlagsStorage::GetOriginListFlag(
const std::string& internal_entry_name) const {
const auto& entry = origin_list_flags_.find(internal_entry_name);
return entry != origin_list_flags_.end() ? entry->second : std::string();
}
void ReadOnlyFlagsStorage::SetOriginListFlag(
const std::string& internal_entry_name,
const std::string& origin_list_value) {}
std::string ReadOnlyFlagsStorage::GetStringFlag(
const std::string& internal_entry_name) const {
return GetOriginListFlag(internal_entry_name);
}
void ReadOnlyFlagsStorage::SetStringFlag(const std::string& internal_entry_name,
const std::string& string_value) {}
FeatureFlagsUpdate::FeatureFlagsUpdate(
const ::flags_ui::FlagsStorage& flags_storage,
PrefService* profile_prefs) {
flags_ = flags_storage.GetFlags();
ApplyUserPolicyToFlags(profile_prefs, &flags_);
for (const auto& flag : flags_) {
const auto origin_list_flag = flags_storage.GetOriginListFlag(flag);
if (!origin_list_flag.empty()) {
origin_list_flags_[flag] = origin_list_flag;
}
}
}
FeatureFlagsUpdate::~FeatureFlagsUpdate() = default;
bool FeatureFlagsUpdate::DiffersFromCommandLine(
base::CommandLine* cmdline,
std::set<std::string>* flags_difference) {
flags_difference->clear();
const auto cmdline_flags = ParseFlagsFromCommandLine(cmdline);
std::set_symmetric_difference(
flags_.begin(), flags_.end(), cmdline_flags.begin(), cmdline_flags.end(),
std::inserter(*flags_difference, flags_difference->begin()));
auto lookup = [](const std::map<std::string, std::string>& origin_list_flags,
const std::string& key) {
const auto entry = origin_list_flags.find(key);
return entry == origin_list_flags.end() ? std::nullopt
: std::make_optional(entry->second);
};
const auto cmdline_origin_list_flags =
ParseOriginListFlagsFromCommmandLine(cmdline);
for (const auto& flag : flags_) {
if (lookup(origin_list_flags_, flag) !=
lookup(cmdline_origin_list_flags, flag)) {
flags_difference->insert(flag);
}
}
return !flags_difference->empty();
}
void FeatureFlagsUpdate::UpdateSessionManager() {
// TODO(crbug.com/832857): Introduce a CHECK to ensure primary user.
// Early out so that switches for secondary users are not applied to the whole
// session. This could be removed when things like flags UI of secondary users
// are fixed properly and TODO above to add CHECK() is done.
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
const user_manager::User* primary_user = user_manager->GetPrimaryUser();
if (!primary_user || primary_user != user_manager->GetActiveUser())
return;
std::set<std::string> flags = flags_;
// If LacrosAvailability policy is set, inject it into the feature flag,
// so that the value is preserved on restarting the Chrome.
// This is a kind of pseudo feature flag, so do not apply it in
// ApplyUserPolicyToFlags to store in |flags_|, otherwise the value will
// be used to decide whether or not to reboot to apply feature flags.
const PrefService::Preference* lacros_launch_switch_pref =
g_browser_process->local_state()->FindPreference(
::prefs::kLacrosLaunchSwitch);
if (lacros_launch_switch_pref->IsManaged()) {
// If there's the value, convert it into the feature name.
std::string_view value =
ash::standalone_browser::GetLacrosAvailabilityPolicyName(
static_cast<ash::standalone_browser::LacrosAvailability>(
lacros_launch_switch_pref->GetValue()->GetInt()));
DCHECK(!value.empty())
<< "The unexpect value is set to LacrosAvailability: "
<< lacros_launch_switch_pref->GetValue()->GetInt();
auto* entry = ::about_flags::GetCurrentFlagsState()->FindFeatureEntryByName(
ash::standalone_browser::kLacrosAvailabilityPolicyInternalName);
DCHECK(entry);
int index;
for (index = 0; index < entry->NumOptions(); ++index) {
if (value == entry->ChoiceForOption(index).command_line_value)
break;
}
if (static_cast<size_t>(index) != entry->choices.size()) {
LOG(ERROR) << "Updating the lacros_availability: " << index;
flags.insert(entry->NameForOption(index));
}
}
const PrefService::Preference* lacros_data_backward_migration_mode_pref =
g_browser_process->local_state()->FindPreference(
::prefs::kLacrosDataBackwardMigrationMode);
if (lacros_data_backward_migration_mode_pref->IsManaged()) {
auto value =
lacros_data_backward_migration_mode_pref->GetValue()->GetString();
auto* entry = ::about_flags::GetCurrentFlagsState()->FindFeatureEntryByName(
crosapi::browser_util::
kLacrosDataBackwardMigrationModePolicyInternalName);
DCHECK(entry);
int index;
for (index = 0; index < entry->NumOptions(); ++index) {
if (value == entry->ChoiceForOption(index).command_line_value)
break;
}
if (static_cast<size_t>(index) != entry->choices.size()) {
LOG(ERROR) << "Updating the lacros_data_backward_migration_mode: "
<< index;
flags.insert(entry->NameForOption(index));
}
}
auto account_id = cryptohome::CreateAccountIdentifierFromAccountId(
primary_user->GetAccountId());
SessionManagerClient::Get()->SetFeatureFlagsForUser(
account_id, {flags.begin(), flags.end()}, origin_list_flags_);
}
// static
void FeatureFlagsUpdate::ApplyUserPolicyToFlags(PrefService* user_profile_prefs,
std::set<std::string>* flags) {
// Get target value for --site-per-process for the user session according to
// policy. If it is supposed to be enabled, make sure it can not be disabled
// using flags-induced command-line switches.
const PrefService::Preference* site_per_process_pref =
user_profile_prefs->FindPreference(::prefs::kSitePerProcess);
if (site_per_process_pref->IsManaged() &&
site_per_process_pref->GetValue()->GetBool()) {
flags->erase(::about_flags::SiteIsolationTrialOptOutChoiceEnabled());
}
}
} // namespace about_flags
} // namespace ash