blob: 569306c89cda7d7dd2477990492709d4318c787e [file] [log] [blame]
// Copyright 2020 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/crosapi/browser_util.h"
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/values_util.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/system/sys_info.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chromeos/ash/components/standalone_browser/browser_support.h"
#include "chromeos/ash/components/standalone_browser/lacros_availability.h"
#include "chromeos/crosapi/cpp/crosapi_constants.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "components/component_updater/component_updater_service.h"
#include "components/exo/shell_surface_util.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "components/version_info/channel.h"
#include "components/version_info/version_info.h"
#include "google_apis/gaia/gaia_auth_util.h"
using ash::standalone_browser::BrowserSupport;
using ash::standalone_browser::IsGoogleInternal;
using ash::standalone_browser::LacrosAvailability;
using user_manager::User;
using user_manager::UserManager;
using version_info::Channel;
namespace crosapi {
namespace browser_util {
namespace {
bool g_profile_migration_completed_for_test = false;
absl::optional<bool> g_lacros_primary_browser_for_test;
// At session start the value for LacrosAvailability logic is applied and the
// result is stored in this variable which is used after that as a cache.
absl::optional<LacrosAvailability> g_lacros_availability_cache;
// At session start the value for LacrosDataBackwardMigrationMode logic is
// applied and the result is stored in this variable which is used after that as
// a cache.
absl::optional<LacrosDataBackwardMigrationMode>
g_lacros_data_backward_migration_mode;
// At session start the value for LacrosSelection logic is applied and the
// result is stored in this variable which is used after that as a cache.
absl::optional<LacrosSelectionPolicy> g_lacros_selection_cache;
// The rootfs lacros-chrome metadata keys.
constexpr char kLacrosMetadataContentKey[] = "content";
constexpr char kLacrosMetadataVersionKey[] = "version";
// The conversion map for LacrosDataBackwardMigrationMode policy data. The
// values must match the ones from LacrosDataBackwardMigrationMode.yaml.
constexpr auto kLacrosDataBackwardMigrationModeMap =
base::MakeFixedFlatMap<base::StringPiece, LacrosDataBackwardMigrationMode>({
{kLacrosDataBackwardMigrationModePolicyNone,
LacrosDataBackwardMigrationMode::kNone},
{kLacrosDataBackwardMigrationModePolicyKeepNone,
LacrosDataBackwardMigrationMode::kKeepNone},
{kLacrosDataBackwardMigrationModePolicyKeepSafeData,
LacrosDataBackwardMigrationMode::kKeepSafeData},
{kLacrosDataBackwardMigrationModePolicyKeepAll,
LacrosDataBackwardMigrationMode::kKeepAll},
});
// The conversion map for LacrosSelection policy data. The values must match
// the ones from LacrosSelection.yaml.
constexpr auto kLacrosSelectionPolicyMap =
base::MakeFixedFlatMap<base::StringPiece, LacrosSelectionPolicy>({
{"user_choice", LacrosSelectionPolicy::kUserChoice},
{"rootfs", LacrosSelectionPolicy::kRootfs},
});
// Some account types require features that aren't yet supported by lacros.
// See https://crbug.com/1080693
bool IsUserTypeAllowed(const User* user) {
switch (user->GetType()) {
case user_manager::USER_TYPE_REGULAR:
case user_manager::USER_TYPE_PUBLIC_ACCOUNT:
// Note: Lacros will not be enabled for Guest users unless LacrosSupport
// flag is passed in --enable-features. See https://crbug.com/1294051#c25.
case user_manager::USER_TYPE_GUEST:
return true;
case user_manager::USER_TYPE_CHILD:
return base::FeatureList::IsEnabled(kLacrosForSupervisedUsers);
case user_manager::USER_TYPE_WEB_KIOSK_APP:
return base::FeatureList::IsEnabled(features::kWebKioskEnableLacros);
case user_manager::USER_TYPE_KIOSK_APP:
return base::FeatureList::IsEnabled(features::kChromeKioskEnableLacros);
case user_manager::USER_TYPE_ARC_KIOSK_APP:
case user_manager::USER_TYPE_ACTIVE_DIRECTORY:
case user_manager::NUM_USER_TYPES:
return false;
}
}
// Returns the lacros integration suggested by the policy lacros-availability.
// There are several reasons why we might choose to ignore the
// lacros-availability policy.
// 1. The user has set a command line or chrome://flag for
// kLacrosAvailabilityIgnore.
// 2. The user is a Googler and they are not opted into the
// kLacrosGooglePolicyRollout trial and they did not have the
// kLacrosDisallowed policy.
LacrosAvailability GetCachedLacrosAvailability() {
// TODO(crbug.com/1286340): add DCHECK for production use to avoid the
// same inconsistency for the future.
if (g_lacros_availability_cache.has_value())
return g_lacros_availability_cache.value();
// It could happen in some browser tests that value is not cached. Return
// default in that case.
return LacrosAvailability::kUserChoice;
}
// Gets called from IsLacrosAllowedToBeEnabled with primary user or from
// IsLacrosEnabledForMigration with the user that the
// IsLacrosEnabledForMigration was passed.
bool IsLacrosAllowedToBeEnabledWithUser(
const User* user,
LacrosAvailability launch_availability) {
if (BrowserSupport::GetLacrosEnabledForTest()) {
return true;
}
if (!IsUserTypeAllowed(user)) {
return false;
}
switch (launch_availability) {
case LacrosAvailability::kUserChoice:
break;
case LacrosAvailability::kLacrosDisallowed:
return false;
case LacrosAvailability::kSideBySide:
case LacrosAvailability::kLacrosPrimary:
case LacrosAvailability::kLacrosOnly:
return true;
}
return true;
}
// Called from `IsDataWipeRequired()` or `IsDataWipeRequiredForTesting()`.
// data_version` is the version of last data wipe. `current_version` is the
// version of ash-chrome. `required_version` is the version that introduces some
// breaking change. `data_version` needs to be greater or equal to
// `required_version`. If `required_version` is newer than `current_version`,
// data wipe is not required.
bool IsDataWipeRequiredInternal(base::Version data_version,
const base::Version& current_version,
const base::Version& required_version) {
// `data_version` is invalid if any wipe has not been recorded yet. In
// such a case, assume that the last data wipe happened significantly long
// time ago.
if (!data_version.IsValid())
data_version = base::Version("0");
if (current_version < required_version) {
// If `current_version` is smaller than the `required_version`, that means
// that the data wipe doesn't need to happen yet.
return false;
}
if (data_version >= required_version) {
// If `data_version` is greater or equal to `required_version`, this means
// data wipe has already happened and that user data is compatible with the
// current lacros.
return false;
}
return true;
}
// Returns the string value for the kLacrosStabilitySwitch if present.
absl::optional<std::string> GetLacrosStabilitySwitchValue() {
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
return cmdline->HasSwitch(browser_util::kLacrosStabilitySwitch)
? absl::optional<std::string>(cmdline->GetSwitchValueASCII(
browser_util::kLacrosStabilitySwitch))
: absl::nullopt;
}
// Resolves the Lacros stateful channel in the following order:
// 1. From the kLacrosStabilitySwitch command line flag if present.
// 2. From the current ash channel.
Channel GetStatefulLacrosChannel() {
static const auto kStabilitySwitchToChannelMap =
base::MakeFixedFlatMap<base::StringPiece, Channel>({
{browser_util::kLacrosStabilityChannelCanary, Channel::CANARY},
{browser_util::kLacrosStabilityChannelDev, Channel::DEV},
{browser_util::kLacrosStabilityChannelBeta, Channel::BETA},
{browser_util::kLacrosStabilityChannelStable, Channel::STABLE},
});
auto stability_switch_value = GetLacrosStabilitySwitchValue();
return stability_switch_value && base::Contains(kStabilitySwitchToChannelMap,
*stability_switch_value)
? kStabilitySwitchToChannelMap.at(*stability_switch_value)
: chrome::GetChannel();
}
} // namespace
// NOTE: If you change the lacros component names, you must also update
// chrome/browser/component_updater/cros_component_installer_chromeos.cc
const ComponentInfo kLacrosDogfoodCanaryInfo = {
"lacros-dogfood-canary", "hkifppleldbgkdlijbdfkdpedggaopda"};
const ComponentInfo kLacrosDogfoodDevInfo = {
"lacros-dogfood-dev", "ldobopbhiamakmncndpkeelenhdmgfhk"};
const ComponentInfo kLacrosDogfoodBetaInfo = {
"lacros-dogfood-beta", "hnfmbeciphpghlfgpjfbcdifbknombnk"};
const ComponentInfo kLacrosDogfoodStableInfo = {
"lacros-dogfood-stable", "ehpjbaiafkpkmhjocnenjbbhmecnfcjb"};
// A kill switch for lacros chrome apps.
BASE_FEATURE(kLacrosDisableChromeApps,
"LacrosDisableChromeApps",
base::FEATURE_DISABLED_BY_DEFAULT);
// Makes LaCrOS allowed for Family Link users.
// With this feature disabled LaCrOS cannot be enabled for Family Link users.
// When this feature is enabled LaCrOS availability is a under control of other
// launch switches.
// Note: Family Link users do not have access to chrome://flags and this feature
// flag is meant to help with development and testing.
BASE_FEATURE(kLacrosForSupervisedUsers,
"LacrosForSupervisedUsers",
base::FEATURE_DISABLED_BY_DEFAULT);
const Channel kLacrosDefaultChannel = Channel::DEV;
const char kLacrosStabilitySwitch[] = "lacros-stability";
const char kLacrosStabilityChannelCanary[] = "canary";
const char kLacrosStabilityChannelDev[] = "dev";
const char kLacrosStabilityChannelBeta[] = "beta";
const char kLacrosStabilityChannelStable[] = "stable";
const char kLacrosSelectionSwitch[] = "lacros-selection";
const char kLacrosSelectionRootfs[] = "rootfs";
const char kLacrosSelectionStateful[] = "stateful";
// The internal name in about_flags.cc for the lacros-availablility-policy
// config.
const char kLacrosAvailabilityPolicyInternalName[] =
"lacros-availability-policy";
// The commandline flag name of lacros-availability-policy.
// The value should be the policy value as defined just below.
// The values need to be consistent with kLacrosAvailabilityMap above.
const char kLacrosAvailabilityPolicySwitch[] = "lacros-availability-policy";
const char kLacrosAvailabilityPolicyUserChoice[] = "user_choice";
const char kLacrosAvailabilityPolicyLacrosDisabled[] = "lacros_disabled";
const char kLacrosAvailabilityPolicySideBySide[] = "side_by_side";
const char kLacrosAvailabilityPolicyLacrosPrimary[] = "lacros_primary";
const char kLacrosAvailabilityPolicyLacrosOnly[] = "lacros_only";
const char kLaunchOnLoginPref[] = "lacros.launch_on_login";
const char kClearUserDataDir1Pref[] = "lacros.clear_user_data_dir_1";
const char kDataVerPref[] = "lacros.data_version";
const char kRequiredDataVersion[] = "92.0.0.0";
const char kProfileMigrationCompletedForUserPref[] =
"lacros.profile_migration_completed_for_user";
const char kProfileMoveMigrationCompletedForUserPref[] =
"lacros.profile_move_migration_completed_for_user";
const char kProfileDataBackwardMigrationCompletedForUserPref[] =
"lacros.profile_data_backward_migration_completed_for_user";
// This pref is to record whether the user clicks "Go to files" button
// on error page of the data migration.
const char kGotoFilesPref[] = "lacros.goto_files";
const char kProfileMigrationCompletionTimeForUserPref[] =
"lacros.profile_migration_completion_time_for_user";
void RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(kLaunchOnLoginPref, /*default_value=*/false);
registry->RegisterBooleanPref(kClearUserDataDir1Pref,
/*default_value=*/false);
}
void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(kDataVerPref);
registry->RegisterDictionaryPref(kProfileMigrationCompletedForUserPref,
base::Value::Dict());
registry->RegisterDictionaryPref(kProfileMoveMigrationCompletedForUserPref,
base::Value::Dict());
registry->RegisterDictionaryPref(
kProfileDataBackwardMigrationCompletedForUserPref, base::Value::Dict());
registry->RegisterListPref(kGotoFilesPref);
registry->RegisterDictionaryPref(kProfileMigrationCompletionTimeForUserPref,
base::Value::Dict());
}
base::FilePath GetUserDataDir() {
if (base::SysInfo::IsRunningOnChromeOS()) {
// NOTE: On device this function is privacy/security sensitive. The
// directory must be inside the encrypted user partition.
return base::FilePath(crosapi::kLacrosUserDataPath);
}
// For developers on Linux desktop, put the directory under the developer's
// specified --user-data-dir.
base::FilePath base_path;
base::PathService::Get(chrome::DIR_USER_DATA, &base_path);
return base_path.Append("lacros");
}
bool IsLacrosAllowedToBeEnabled() {
// Allows tests to avoid enabling the flag, constructing a fake user manager,
// creating g_browser_process->local_state(), etc.
if (BrowserSupport::GetLacrosEnabledForTest()) {
return true;
}
// TODO(crbug.com/1185813): TaskManagerImplTest is not ready to run with
// Lacros enabled.
// UserManager is not initialized for unit tests by default, unless a fake
// user manager is constructed.
if (!UserManager::IsInitialized()) {
return false;
}
// GetPrimaryUser works only after user session is started.
const User* user = UserManager::Get()->GetPrimaryUser();
if (!user) {
return false;
}
return IsLacrosAllowedToBeEnabledWithUser(user,
GetCachedLacrosAvailability());
}
bool IsLacrosEnabled() {
// Allows tests to avoid enabling the flag, constructing a fake user manager,
// creating g_browser_process->local_state(), etc.
if (BrowserSupport::GetLacrosEnabledForTest()) {
return true;
}
if (!IsLacrosAllowedToBeEnabled())
return false;
// If profile migration is enabled, the completion of it is necessary for
// Lacros to be enabled.
if (IsProfileMigrationEnabled()) {
PrefService* local_state = g_browser_process->local_state();
// Note that local_state can be nullptr in tests.
if (local_state &&
!IsCopyOrMoveProfileMigrationCompletedForUser(
local_state,
UserManager::Get()->GetPrimaryUser()->username_hash())) {
// If migration has not been completed, do not enable lacros.
return false;
}
}
switch (GetCachedLacrosAvailability()) {
case LacrosAvailability::kUserChoice:
break;
case LacrosAvailability::kLacrosDisallowed:
return false;
case LacrosAvailability::kSideBySide:
case LacrosAvailability::kLacrosPrimary:
case LacrosAvailability::kLacrosOnly:
return true;
}
return base::FeatureList::IsEnabled(ash::features::kLacrosSupport);
}
bool IsLacrosEnabledForMigration(const User* user,
PolicyInitState policy_init_state) {
if (BrowserSupport::GetLacrosEnabledForTest()) {
return true;
}
LacrosAvailability lacros_availability;
if (policy_init_state == PolicyInitState::kBeforeInit) {
// Before Policy is initialized, the value won't be available.
// So, we'll use the value preserved in the feature flags.
// See also LacrosAvailabilityPolicyObserver how it will be propergated.
lacros_availability =
ash::standalone_browser::DetermineLacrosAvailabilityFromPolicyValue(
user, base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kLacrosAvailabilityPolicySwitch));
} else {
DCHECK_EQ(policy_init_state, PolicyInitState::kAfterInit);
lacros_availability = GetCachedLacrosAvailability();
}
if (!IsLacrosAllowedToBeEnabledWithUser(user, lacros_availability))
return false;
switch (lacros_availability) {
case LacrosAvailability::kUserChoice:
break;
case LacrosAvailability::kLacrosDisallowed:
return false;
case LacrosAvailability::kSideBySide:
case LacrosAvailability::kLacrosPrimary:
case LacrosAvailability::kLacrosOnly:
return true;
}
return base::FeatureList::IsEnabled(ash::features::kLacrosSupport);
}
bool IsProfileMigrationEnabled() {
const UserManager* user_manager = UserManager::Get();
if (!user_manager) {
return false;
}
const User* user = user_manager->GetPrimaryUser();
if (!user) {
return false;
}
return IsProfileMigrationEnabledWithUserAndPolicyInitState(
user, PolicyInitState::kAfterInit);
}
bool IsProfileMigrationEnabledWithUserAndPolicyInitState(
const user_manager::User* user,
PolicyInitState policy_init_state) {
return !base::FeatureList::IsEnabled(
ash::features::kLacrosProfileMigrationForceOff) &&
!IsAshWebBrowserEnabledForMigration(user, policy_init_state);
}
bool IsProfileMigrationAvailable() {
if (!IsProfileMigrationEnabled()) {
return false;
}
// If migration is already completed, it is not necessary to run again.
if (IsCopyOrMoveProfileMigrationCompletedForUser(
UserManager::Get()->GetLocalState(),
UserManager::Get()->GetPrimaryUser()->username_hash())) {
return false;
}
return true;
}
bool IsLacrosSupportFlagAllowed() {
return IsLacrosAllowedToBeEnabled() &&
(GetCachedLacrosAvailability() == LacrosAvailability::kUserChoice);
}
bool IsAshWebBrowserEnabled() {
// Note that if you are updating this function, please also update the
// *ForMigration variant to keep the logics consistent.
// If Lacros is not a primary browser, Ash browser is always enabled.
if (!IsLacrosPrimaryBrowser())
return true;
switch (GetCachedLacrosAvailability()) {
case LacrosAvailability::kUserChoice:
break;
case LacrosAvailability::kLacrosDisallowed:
return true;
case LacrosAvailability::kSideBySide:
case LacrosAvailability::kLacrosPrimary:
// Normally, policy should override Finch. Due to complications in the
// Google rollout, in the short term Finch will override policy if Finch
// is enabling this feature.
if (IsGoogleInternal(UserManager::Get()->GetPrimaryUser()) &&
base::FeatureList::IsEnabled(ash::features::kLacrosOnly)) {
return false;
}
return true;
case LacrosAvailability::kLacrosOnly:
return false;
}
return !base::FeatureList::IsEnabled(ash::features::kLacrosOnly);
}
bool IsAshWebBrowserEnabledForMigration(const user_manager::User* user,
PolicyInitState policy_init_state) {
// If Lacros is not a primary browser, Ash browser is always enabled.
if (!IsLacrosPrimaryBrowserForMigration(user, policy_init_state))
return true;
LacrosAvailability lacros_availability;
if (policy_init_state == PolicyInitState::kBeforeInit) {
// Before Policy is initialized, the value won't be available.
// So, we'll use the value preserved in the feature flags.
// See also LacrosAvailabilityPolicyObserver how it will be propergated.
lacros_availability =
ash::standalone_browser::DetermineLacrosAvailabilityFromPolicyValue(
user, base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kLacrosAvailabilityPolicySwitch));
} else {
DCHECK_EQ(policy_init_state, PolicyInitState::kAfterInit);
lacros_availability = GetCachedLacrosAvailability();
}
switch (lacros_availability) {
case LacrosAvailability::kUserChoice:
break;
case LacrosAvailability::kLacrosDisallowed:
return true;
case LacrosAvailability::kSideBySide:
case LacrosAvailability::kLacrosPrimary:
// Note that for this *ForMigration variant, since there might not be a
// logged in user yet, the user's email address has to be passed
// explicitly. Normally, policy should override Finch. Due to
// complications in the Google rollout, in the short term Finch will
// override policy if Finch is enabling this feature.
if (gaia::IsGoogleInternalAccountEmail(
user->GetAccountId().GetUserEmail()) &&
base::FeatureList::IsEnabled(ash::features::kLacrosOnly)) {
return false;
}
return true;
case LacrosAvailability::kLacrosOnly:
return false;
}
return !base::FeatureList::IsEnabled(ash::features::kLacrosOnly);
}
bool IsLacrosPrimaryBrowser() {
// Note that if you are updating this function, please also update the
// *ForMigration variant to keep the logics consistent.
if (g_lacros_primary_browser_for_test.has_value())
return g_lacros_primary_browser_for_test.value();
if (!IsLacrosEnabled())
return false;
// Lacros-chrome will always be the primary browser if Lacros is enabled in
// Kiosk session.
if (UserManager::Get()->IsLoggedInAsWebKioskApp() ||
UserManager::Get()->IsLoggedInAsKioskApp()) {
return true;
}
if (!IsLacrosPrimaryBrowserAllowed())
return false;
switch (GetCachedLacrosAvailability()) {
case LacrosAvailability::kUserChoice:
break;
case LacrosAvailability::kLacrosDisallowed:
NOTREACHED();
return false;
case LacrosAvailability::kSideBySide:
return false;
case LacrosAvailability::kLacrosPrimary:
case LacrosAvailability::kLacrosOnly:
return true;
}
return base::FeatureList::IsEnabled(ash::features::kLacrosPrimary);
}
bool IsLacrosPrimaryBrowserForMigration(const user_manager::User* user,
PolicyInitState policy_init_state) {
if (g_lacros_primary_browser_for_test.has_value())
return g_lacros_primary_browser_for_test.value();
if (!IsLacrosEnabledForMigration(user, policy_init_state))
return false;
// Lacros-chrome will always be the primary browser if Lacros is enabled in
// web Kiosk session.
if (user->GetType() == user_manager::USER_TYPE_KIOSK_APP ||
user->GetType() == user_manager::USER_TYPE_WEB_KIOSK_APP) {
return true;
}
LacrosAvailability lacros_availability;
if (policy_init_state == PolicyInitState::kBeforeInit) {
// Before Policy is initialized, the value won't be available.
// So, we'll use the value preserved in the feature flags.
// See also LacrosAvailabilityPolicyObserver how it will be propergated.
lacros_availability =
ash::standalone_browser::DetermineLacrosAvailabilityFromPolicyValue(
user, base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kLacrosAvailabilityPolicySwitch));
} else {
DCHECK_EQ(policy_init_state, PolicyInitState::kAfterInit);
lacros_availability = GetCachedLacrosAvailability();
}
if (!IsLacrosPrimaryBrowserAllowedForMigration(user, lacros_availability))
return false;
switch (lacros_availability) {
case LacrosAvailability::kUserChoice:
break;
case LacrosAvailability::kLacrosDisallowed:
NOTREACHED();
return false;
case LacrosAvailability::kSideBySide:
return false;
case LacrosAvailability::kLacrosPrimary:
case LacrosAvailability::kLacrosOnly:
return true;
}
return base::FeatureList::IsEnabled(ash::features::kLacrosPrimary);
}
LacrosMode GetLacrosMode() {
if (!IsAshWebBrowserEnabled())
return LacrosMode::kOnly;
if (IsLacrosPrimaryBrowser())
return LacrosMode::kPrimary;
if (IsLacrosEnabled())
return LacrosMode::kSideBySide;
return LacrosMode::kDisabled;
}
bool IsLacrosPrimaryBrowserAllowed() {
// Note that if you are updating this function, please also update the
// *ForMigration variant to keep the logics consistent.
if (!IsLacrosAllowedToBeEnabled())
return false;
switch (GetCachedLacrosAvailability()) {
case LacrosAvailability::kLacrosDisallowed:
return false;
case LacrosAvailability::kLacrosPrimary:
case LacrosAvailability::kLacrosOnly:
// Forcibly allow to use Lacros as a Primary respecting the policy.
return true;
default:
// Fallback others.
break;
}
return true;
}
bool IsLacrosPrimaryBrowserAllowedForMigration(
const user_manager::User* user,
LacrosAvailability lacros_availability) {
if (!IsLacrosAllowedToBeEnabledWithUser(user, lacros_availability))
return false;
switch (lacros_availability) {
case LacrosAvailability::kLacrosDisallowed:
return false;
case LacrosAvailability::kLacrosPrimary:
case LacrosAvailability::kLacrosOnly:
// Forcibly allow to use Lacros as a Primary respecting the policy.
return true;
default:
// Fallback others.
break;
}
return true;
}
bool IsLacrosPrimaryFlagAllowed() {
return IsLacrosPrimaryBrowserAllowed() &&
(GetCachedLacrosAvailability() == LacrosAvailability::kUserChoice);
}
bool IsLacrosOnlyBrowserAllowed() {
if (!IsLacrosAllowedToBeEnabled())
return false;
switch (GetCachedLacrosAvailability()) {
case LacrosAvailability::kLacrosDisallowed:
return false;
case LacrosAvailability::kLacrosOnly:
// Forcibly allow to use Lacros as a Primary respecting the policy.
return true;
case LacrosAvailability::kUserChoice:
case LacrosAvailability::kSideBySide:
case LacrosAvailability::kLacrosPrimary:
// Fallback others.
break;
}
return true;
}
bool IsLacrosOnlyFlagAllowed() {
return IsLacrosOnlyBrowserAllowed() &&
(GetCachedLacrosAvailability() == LacrosAvailability::kUserChoice);
}
bool IsLacrosAllowedToLaunch() {
return UserManager::Get()->GetLoggedInUsers().size() == 1;
}
bool IsLacrosChromeAppsEnabled() {
if (base::FeatureList::IsEnabled(kLacrosDisableChromeApps))
return false;
if (!IsLacrosPrimaryBrowser())
return false;
return true;
}
bool IsLacrosEnabledInWebKioskSession() {
return UserManager::Get()->IsLoggedInAsWebKioskApp() && IsLacrosEnabled();
}
bool IsLacrosEnabledInChromeKioskSession() {
return UserManager::Get()->IsLoggedInAsKioskApp() && IsLacrosEnabled();
}
bool IsLacrosWindow(const aura::Window* window) {
const std::string* app_id = exo::GetShellApplicationId(window);
if (!app_id)
return false;
return base::StartsWith(*app_id, kLacrosAppIdPrefix);
}
// Assuming the metadata exists, parse the version and check if it contains the
// non-backwards-compatible account_manager change.
// A typical format for metadata is:
// {
// "content": {
// "version": "91.0.4469.5"
// },
// "metadata_version": 1
// }
bool DoesMetadataSupportNewAccountManager(base::Value* metadata) {
if (!metadata)
return false;
std::string* version_str =
metadata->GetDict().FindStringByDottedPath("content.version");
if (!version_str) {
return false;
}
std::vector<std::string> versions_str = base::SplitString(
*version_str, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (versions_str.size() != 4)
return false;
int major_version = 0;
int minor_version = 0;
if (!base::StringToInt(versions_str[0], &major_version))
return false;
if (!base::StringToInt(versions_str[2], &minor_version))
return false;
// TODO(https://crbug.com/1197220): Come up with more appropriate major/minor
// version numbers.
return major_version >= 1000 && minor_version >= 0;
}
base::Version GetDataVer(PrefService* local_state,
const std::string& user_id_hash) {
const base::Value::Dict& data_versions = local_state->GetDict(kDataVerPref);
const std::string* data_version_str = data_versions.FindString(user_id_hash);
if (!data_version_str)
return base::Version();
return base::Version(*data_version_str);
}
void RecordDataVer(PrefService* local_state,
const std::string& user_id_hash,
const base::Version& version) {
DCHECK(version.IsValid());
ScopedDictPrefUpdate update(local_state, kDataVerPref);
base::Value::Dict& dict = update.Get();
dict.Set(user_id_hash, version.GetString());
}
bool IsDataWipeRequired(PrefService* local_state,
const std::string& user_id_hash) {
base::Version data_version = GetDataVer(local_state, user_id_hash);
const base::Version& current_version = version_info::GetVersion();
base::Version required_version =
base::Version(base::StringPiece(kRequiredDataVersion));
return IsDataWipeRequiredInternal(data_version, current_version,
required_version);
}
bool IsDataWipeRequiredForTesting(base::Version data_version,
const base::Version& current_version,
const base::Version& required_version) {
return IsDataWipeRequiredInternal(data_version, current_version,
required_version);
}
base::Version GetRootfsLacrosVersionMayBlock(
const base::FilePath& version_file_path) {
if (!base::PathExists(version_file_path)) {
LOG(WARNING) << "The rootfs lacros-chrome metadata is missing.";
return {};
}
std::string metadata;
if (!base::ReadFileToString(version_file_path, &metadata)) {
PLOG(WARNING) << "Failed to read rootfs lacros-chrome metadata.";
return {};
}
absl::optional<base::Value> v = base::JSONReader::Read(metadata);
if (!v || !v->is_dict()) {
LOG(WARNING) << "Failed to parse rootfs lacros-chrome metadata.";
return {};
}
const base::Value::Dict& dict = v->GetDict();
const base::Value::Dict* content = dict.FindDict(kLacrosMetadataContentKey);
if (!content) {
LOG(WARNING)
<< "Failed to parse rootfs lacros-chrome metadata content key.";
return {};
}
const std::string* version = content->FindString(kLacrosMetadataVersionKey);
if (!version) {
LOG(WARNING)
<< "Failed to parse rootfs lacros-chrome metadata version key.";
return {};
}
return base::Version{*version};
}
void CacheLacrosAvailability(const policy::PolicyMap& map) {
if (g_lacros_availability_cache.has_value()) {
// Some browser tests might call this multiple times.
LOG(ERROR) << "Trying to cache LacrosAvailability and the value was set";
return;
}
UserManager* user_manager = UserManager::Get();
const user_manager::User* user = user_manager->GetPrimaryUser();
const base::Value* value =
map.GetValue(policy::key::kLacrosAvailability, base::Value::Type::STRING);
g_lacros_availability_cache =
ash::standalone_browser::DetermineLacrosAvailabilityFromPolicyValue(
user, value ? value->GetString() : base::StringPiece());
}
void CacheLacrosDataBackwardMigrationMode(const policy::PolicyMap& map) {
if (g_lacros_data_backward_migration_mode.has_value()) {
// Some browser tests might call this multiple times.
LOG(ERROR) << "Trying to cache LacrosDataBackwardMigrationMode and the "
"value was set";
return;
}
const base::Value* value = map.GetValue(
policy::key::kLacrosDataBackwardMigrationMode, base::Value::Type::STRING);
g_lacros_data_backward_migration_mode = ParseLacrosDataBackwardMigrationMode(
value ? value->GetString() : base::StringPiece());
}
void CacheLacrosSelection(const policy::PolicyMap& map) {
if (g_lacros_selection_cache.has_value()) {
// Some browser tests might call this multiple times.
LOG(ERROR) << "Trying to cache LacrosSelection and the value was set";
return;
}
// Users can set this switch in chrome://flags to disable the effect of the
// lacros-selection policy. This should only be allows for googlers.
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
if (cmdline->HasSwitch(ash::switches::kLacrosSelectionPolicyIgnore) &&
IsGoogleInternal(UserManager::Get()->GetPrimaryUser())) {
LOG(WARNING) << "LacrosSelection policy is ignored due to the ignore flag";
return;
}
const base::Value* value =
map.GetValue(policy::key::kLacrosSelection, base::Value::Type::STRING);
g_lacros_selection_cache = ParseLacrosSelectionPolicy(
value ? value->GetString() : base::StringPiece());
}
LacrosSelectionPolicy GetCachedLacrosSelectionPolicy() {
return g_lacros_selection_cache.value_or(LacrosSelectionPolicy::kUserChoice);
}
absl::optional<LacrosSelection> DetermineLacrosSelection() {
switch (GetCachedLacrosSelectionPolicy()) {
case LacrosSelectionPolicy::kRootfs:
return LacrosSelection::kRootfs;
case LacrosSelectionPolicy::kUserChoice:
break;
}
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
if (!cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch)) {
return absl::nullopt;
}
auto value =
cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch);
if (value == browser_util::kLacrosSelectionRootfs) {
return LacrosSelection::kRootfs;
}
if (value == browser_util::kLacrosSelectionStateful) {
return LacrosSelection::kStateful;
}
return absl::nullopt;
}
ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel) {
// We default to the Dev component for UNKNOWN channels.
static const auto kChannelToComponentInfoMap =
base::MakeFixedFlatMap<Channel, const ComponentInfo*>({
{Channel::UNKNOWN, &kLacrosDogfoodDevInfo},
{Channel::CANARY, &kLacrosDogfoodCanaryInfo},
{Channel::DEV, &kLacrosDogfoodDevInfo},
{Channel::BETA, &kLacrosDogfoodBetaInfo},
{Channel::STABLE, &kLacrosDogfoodStableInfo},
});
return *kChannelToComponentInfoMap.at(channel);
}
ComponentInfo GetLacrosComponentInfo() {
return GetLacrosComponentInfoForChannel(GetStatefulLacrosChannel());
}
Channel GetLacrosSelectionUpdateChannel(LacrosSelection selection) {
switch (selection) {
case LacrosSelection::kRootfs:
// For 'rootfs' Lacros use the same channel as ash/OS. Obtained from
// the LSB's release track property.
return chrome::GetChannel();
case LacrosSelection::kStateful:
// For 'stateful' Lacros directly check the channel of stateful-lacros
// that the user is on.
return GetStatefulLacrosChannel();
case LacrosSelection::kDeployedLocally:
// For locally deployed Lacros there is no channel so return unknown.
return Channel::UNKNOWN;
}
}
base::Version GetInstalledLacrosComponentVersion(
const component_updater::ComponentUpdateService* component_update_service) {
DCHECK(component_update_service);
const std::vector<component_updater::ComponentInfo>& components =
component_update_service->GetComponents();
const std::string& lacros_component_id = GetLacrosComponentInfo().crx_id;
LOG(WARNING) << "Looking for lacros-chrome component with id: "
<< lacros_component_id;
auto it =
std::find_if(components.begin(), components.end(),
[&](const component_updater::ComponentInfo& component_info) {
return component_info.id == lacros_component_id;
});
return it == components.end() ? base::Version() : it->version;
}
LacrosAvailability GetCachedLacrosAvailabilityForTesting() {
return GetCachedLacrosAvailability();
}
// Returns the cached value of the LacrosDataBackwardMigrationMode policy.
LacrosDataBackwardMigrationMode GetCachedLacrosDataBackwardMigrationMode() {
if (g_lacros_data_backward_migration_mode.has_value())
return g_lacros_data_backward_migration_mode.value();
// By default migration should be disabled.
return LacrosDataBackwardMigrationMode::kNone;
}
void SetLacrosLaunchSwitchSourceForTest(LacrosAvailability test_value) {
g_lacros_availability_cache = test_value;
}
void ClearLacrosAvailabilityCacheForTest() {
g_lacros_availability_cache.reset();
}
void ClearLacrosDataBackwardMigrationModeCacheForTest() {
g_lacros_data_backward_migration_mode.reset();
}
void ClearLacrosSelectionCacheForTest() {
g_lacros_selection_cache.reset();
}
MigrationMode GetMigrationMode(const user_manager::User* user,
PolicyInitState policy_init_state) {
if (base::FeatureList::IsEnabled(
ash::features::kLacrosMoveProfileMigration) ||
!IsAshWebBrowserEnabledForMigration(user, policy_init_state)) {
return MigrationMode::kMove;
}
return MigrationMode::kCopy;
}
bool IsCopyOrMoveProfileMigrationCompletedForUser(
PrefService* local_state,
const std::string& user_id_hash) {
// Completion of profile move migration sets copy migration as completed so
// only checking completion of copy migration is sufficient.
return IsProfileMigrationCompletedForUser(local_state, user_id_hash,
MigrationMode::kCopy);
}
bool IsProfileMigrationCompletedForUser(PrefService* local_state,
const std::string& user_id_hash,
MigrationMode mode) {
// Allows tests to avoid marking profile migration as completed by getting
// user_id_hash of the logged in user and updating
// g_browser_process->local_state() etc.
if (g_profile_migration_completed_for_test)
return true;
std::string pref_name;
switch (mode) {
case MigrationMode::kCopy:
pref_name = kProfileMigrationCompletedForUserPref;
break;
case MigrationMode::kMove:
pref_name = kProfileMoveMigrationCompletedForUserPref;
break;
}
const auto* pref = local_state->FindPreference(pref_name);
// Return if the pref is not registered. This can happen in browsertests. In
// such a case, assume that migration was completed.
if (!pref)
return true;
const base::Value* value = pref->GetValue();
DCHECK(value->is_dict());
absl::optional<bool> is_completed = value->GetDict().FindBool(user_id_hash);
// If migration was skipped or failed, disable lacros.
return is_completed.value_or(false);
}
void SetProfileMigrationCompletedForUser(PrefService* local_state,
const std::string& user_id_hash,
MigrationMode mode) {
ScopedDictPrefUpdate update(local_state,
kProfileMigrationCompletedForUserPref);
update->Set(user_id_hash, true);
if (mode == MigrationMode::kMove) {
ScopedDictPrefUpdate move_update(local_state,
kProfileMoveMigrationCompletedForUserPref);
move_update->Set(user_id_hash, true);
}
}
void ClearProfileMigrationCompletedForUser(PrefService* local_state,
const std::string& user_id_hash) {
{
ScopedDictPrefUpdate update(local_state,
kProfileMigrationCompletedForUserPref);
base::Value::Dict& dict = update.Get();
dict.Remove(user_id_hash);
}
{
ScopedDictPrefUpdate update(local_state,
kProfileMoveMigrationCompletedForUserPref);
base::Value::Dict& dict = update.Get();
dict.Remove(user_id_hash);
}
}
void SetProfileMigrationCompletionTimeForUser(PrefService* local_state,
const std::string& user_id_hash) {
ScopedDictPrefUpdate update(local_state,
kProfileMigrationCompletionTimeForUserPref);
update->Set(user_id_hash, base::TimeToValue(base::Time::Now()));
}
absl::optional<base::Time> GetProfileMigrationCompletionTimeForUser(
PrefService* local_state,
const std::string& user_id_hash) {
const auto* pref =
local_state->FindPreference(kProfileMigrationCompletionTimeForUserPref);
if (!pref) {
return absl::nullopt;
}
const base::Value* value = pref->GetValue();
DCHECK(value->is_dict());
return base::ValueToTime(value->GetDict().Find(user_id_hash));
}
void ClearProfileMigrationCompletionTimeForUser(
PrefService* local_state,
const std::string& user_id_hash) {
ScopedDictPrefUpdate update(local_state,
kProfileMigrationCompletionTimeForUserPref);
base::Value::Dict& dict = update.Get();
dict.Remove(user_id_hash);
}
void SetProfileDataBackwardMigrationCompletedForUser(
PrefService* local_state,
const std::string& user_id_hash) {
ScopedDictPrefUpdate update(
local_state, kProfileDataBackwardMigrationCompletedForUserPref);
update->Set(user_id_hash, true);
}
void ClearProfileDataBackwardMigrationCompletedForUser(
PrefService* local_state,
const std::string& user_id_hash) {
ScopedDictPrefUpdate update(
local_state, kProfileDataBackwardMigrationCompletedForUserPref);
base::Value::Dict& dict = update.Get();
dict.Remove(user_id_hash);
}
void SetProfileMigrationCompletedForTest(bool is_completed) {
g_profile_migration_completed_for_test = is_completed;
}
LacrosLaunchSwitchSource GetLacrosLaunchSwitchSource() {
if (!g_lacros_availability_cache.has_value())
return LacrosLaunchSwitchSource::kUnknown;
// Note: this check needs to be consistent with the one in
// DetermineLacrosAvailabilityFromPolicyValue.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(ash::switches::kLacrosAvailabilityIgnore) &&
IsGoogleInternal(UserManager::Get()->GetPrimaryUser())) {
return LacrosLaunchSwitchSource::kForcedByUser;
}
return GetCachedLacrosAvailability() == LacrosAvailability::kUserChoice
? LacrosLaunchSwitchSource::kPossiblySetByUser
: LacrosLaunchSwitchSource::kForcedByPolicy;
}
absl::optional<LacrosSelectionPolicy> ParseLacrosSelectionPolicy(
base::StringPiece value) {
auto* it = kLacrosSelectionPolicyMap.find(value);
if (it != kLacrosSelectionPolicyMap.end())
return it->second;
LOG(ERROR) << "Unknown LacrosSelection policy value is passed: " << value;
return absl::nullopt;
}
absl::optional<LacrosDataBackwardMigrationMode>
ParseLacrosDataBackwardMigrationMode(base::StringPiece value) {
auto* it = kLacrosDataBackwardMigrationModeMap.find(value);
if (it != kLacrosDataBackwardMigrationModeMap.end())
return it->second;
LOG(ERROR) << "Unknown LacrosDataBackwardMigrationMode policy value: "
<< value;
return absl::nullopt;
}
base::StringPiece GetLacrosDataBackwardMigrationModeName(
LacrosDataBackwardMigrationMode value) {
for (const auto& entry : kLacrosDataBackwardMigrationModeMap) {
if (entry.second == value)
return entry.first;
}
NOTREACHED();
return base::StringPiece();
}
base::StringPiece GetLacrosSelectionPolicyName(LacrosSelectionPolicy value) {
for (const auto& entry : kLacrosSelectionPolicyMap) {
if (entry.second == value) {
return entry.first;
}
}
NOTREACHED();
return base::StringPiece();
}
bool IsAshBrowserSyncEnabled() {
// Turn off sync from Ash if Lacros is enabled and Ash web browser is
// disabled.
// TODO(crbug.com/1293250): We must check whether profile migration is
// completed or not here. Currently that is checked inside `IsLacrosEnabled()`
// but it is planned to be decoupled with the function in the future.
if (IsLacrosEnabled() && !IsAshWebBrowserEnabled())
return false;
return true;
}
void SetGotoFilesClicked(PrefService* local_state,
const std::string& user_id_hash) {
ScopedListPrefUpdate update(local_state, kGotoFilesPref);
base::Value::List& list = update.Get();
base::Value user_id_hash_value(user_id_hash);
if (!base::Contains(list, user_id_hash_value))
list.Append(std::move(user_id_hash_value));
}
void ClearGotoFilesClicked(PrefService* local_state,
const std::string& user_id_hash) {
ScopedListPrefUpdate update(local_state, kGotoFilesPref);
update->EraseValue(base::Value(user_id_hash));
}
bool WasGotoFilesClicked(PrefService* local_state,
const std::string& user_id_hash) {
const base::Value::List& list = local_state->GetList(kGotoFilesPref);
return base::Contains(list, base::Value(user_id_hash));
}
bool ShouldEnforceAshExtensionKeepList() {
return IsLacrosPrimaryBrowser() &&
base::FeatureList::IsEnabled(
ash::features::kEnforceAshExtensionKeeplist);
}
base::AutoReset<absl::optional<bool>>
SetLacrosPrimaryBrowserForTest( // IN-TEST
absl::optional<bool> value) {
return base::AutoReset<absl::optional<bool>>(
&g_lacros_primary_browser_for_test, value);
}
} // namespace browser_util
} // namespace crosapi