blob: 21284a6e9e5a5f60776e7a18bba01093cead31d9 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/arc/arc_util.h"
#include <linux/magic.h>
#include <sys/statfs.h>
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/threading/scoped_blocking_call.h"
#include "chrome/browser/chromeos/arc/arc_session_manager.h"
#include "chrome/browser/chromeos/arc/policy/arc_policy_util.h"
#include "chrome/browser/chromeos/login/configuration_keys.h"
#include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
#include "chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h"
#include "chrome/browser/chromeos/login/oobe_configuration.h"
#include "chrome/browser/chromeos/login/ui/login_display_host.h"
#include "chrome/browser/chromeos/login/user_flow.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/arc/arc_features.h"
#include "components/arc/arc_prefs.h"
#include "components/arc/arc_util.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "third_party/icu/source/common/unicode/locid.h"
namespace arc {
namespace {
// Contains map of profile to check result of ARC allowed. Contains true if ARC
// allowed check was performed and ARC is allowed. If map does not contain
// a value then this means that check has not been performed yet.
base::LazyInstance<std::map<const Profile*, bool>>::DestructorAtExit
g_profile_status_check = LAZY_INSTANCE_INITIALIZER;
// The cached value of migration allowed for profile. It is necessary to use
// the same value during a user session.
base::LazyInstance<std::map<base::FilePath, bool>>::DestructorAtExit
g_is_arc_migration_allowed = LAZY_INSTANCE_INITIALIZER;
// Let IsAllowedForProfile() return "false" for any profile.
bool g_disallow_for_testing = false;
// Let IsArcBlockedDueToIncompatibleFileSystem() return the specified value
// during test runs. Doesn't affect ARC kiosk and public session.
bool g_arc_blocked_due_to_incompatible_filesystem_for_testing = false;
// TODO(kinaba): Temporary workaround for
// Some type of accounts don't have user prefs. As a short-term workaround,
// store the compatibility info from them on memory, ignoring the defect that
// it cannot survive browser crash and restart.
// This will be removed once the forced migration for ARC Kiosk user is
// implemented. After it's done such types of accounts cannot even sign-in
// with incompatible filesystem. Hence it'll be safe to always regard compatible
// for them then.
g_known_compatible_users = LAZY_INSTANCE_INITIALIZER;
// Returns whether ARC can run on the filesystem mounted at |path|.
// This function should run only on threads where IO operations are allowed.
bool IsArcCompatibleFilesystem(const base::FilePath& path) {
base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
// If it can be verified it is not on ecryptfs, then it is ok.
struct statfs statfs_buf;
if (statfs(path.value().c_str(), &statfs_buf) < 0)
return false;
return statfs_buf.f_type != ECRYPTFS_SUPER_MAGIC;
FileSystemCompatibilityState GetFileSystemCompatibilityPref(
const AccountId& account_id) {
int pref_value = kFileSystemIncompatible;
account_id, prefs::kArcCompatibleFilesystemChosen, &pref_value);
return static_cast<FileSystemCompatibilityState>(pref_value);
// Stores the result of IsArcCompatibleFilesystem posted back from the blocking
// task runner.
void StoreCompatibilityCheckResult(const AccountId& account_id,
base::OnceClosure callback,
bool is_compatible) {
if (is_compatible) {
account_id, prefs::kArcCompatibleFilesystemChosen,
// TODO(kinaba): Remove this code for accounts without user prefs.
// See the comment for |g_known_compatible_users| for the detail.
if (GetFileSystemCompatibilityPref(account_id) != kFileSystemCompatible)
bool IsArcMigrationAllowedInternal(const Profile* profile) {
policy_util::EcryptfsMigrationAction migration_strategy =
if (profile->GetPrefs()->IsManagedPreference(
prefs::kEcryptfsMigrationStrategy)) {
migration_strategy = static_cast<policy_util::EcryptfsMigrationAction>(
// |kAskForEcryptfsArcUsers| value is received only if the device is in EDU
// and admin left the migration policy unset. Note that when enabling ARC on
// the admin console, it is mandatory for the administrator to also choose a
// migration policy.
// In this default case, only a group of devices that had ARC M enabled are
// allowed to migrate, provided that ARC is enabled by policy.
// TODO(pmarko): Remove the special kAskForEcryptfsArcUsers handling when we
// assess that it's not necessary anymore:
if (migration_strategy ==
policy_util::EcryptfsMigrationAction::kAskForEcryptfsArcUsers) {
// Note that ARC enablement is controlled by policy for managed users (as
// it's marked 'default_for_enterprise_users': False in
// policy_templates.json).
// We can't reuse IsArcPlayStoreEnabledForProfile here because this would
// lead to a circular dependency: It ends up calling this function for some
// cases.
return profile->GetPrefs()->GetBoolean(prefs::kArcEnabled) &&
return migration_strategy !=
bool IsUnaffiliatedArcAllowed() {
bool arc_allowed;
ArcSessionManager* arc_session_manager = ArcSessionManager::Get();
if (arc_session_manager) {
switch (arc_session_manager->state()) {
case ArcSessionManager::State::NOT_INITIALIZED:
case ArcSessionManager::State::STOPPED:
// Apply logic below
case ArcSessionManager::State::NEGOTIATING_TERMS_OF_SERVICE:
case ArcSessionManager::State::CHECKING_ANDROID_MANAGEMENT:
case ArcSessionManager::State::REMOVING_DATA_DIR:
case ArcSessionManager::State::ACTIVE:
case ArcSessionManager::State::STOPPING:
// Never forbid unaffiliated ARC while ARC is running
return true;
if (chromeos::CrosSettings::Get()->GetBoolean(
chromeos::kUnaffiliatedArcAllowed, &arc_allowed)) {
return arc_allowed;
// If device policy is not set, allow ARC.
return true;
bool IsArcAllowedForProfileInternal(const Profile* profile,
bool should_report_reason) {
if (g_disallow_for_testing) {
VLOG_IF(1, should_report_reason) << "ARC is disallowed for testing.";
return false;
// ARC Kiosk can be enabled even if ARC is not yet supported on the device.
// In that case IsArcKioskMode() should return true as profile is already
// created.
if (!IsArcAvailable() && !(IsArcKioskMode() && IsArcKioskAvailable())) {
VLOG_IF(1, should_report_reason) << "ARC is not available.";
return false;
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile)) {
VLOG_IF(1, should_report_reason)
<< "Non-primary users are not supported in ARC.";
return false;
if (profile->IsLegacySupervised()) {
VLOG_IF(1, should_report_reason)
<< "Supervised users are not supported in ARC.";
return false;
if (IsArcBlockedDueToIncompatibleFileSystem(profile) &&
!IsArcMigrationAllowedByPolicyForProfile(profile)) {
VLOG_IF(1, should_report_reason)
<< "Incompatible encryption and migration forbidden.";
return false;
// Play Store requires an appropriate application install mechanism. Normal
// users do this through GAIA, but Kiosk and Active Directory users use
// different application install mechanism. ARC is not allowed otherwise
// (e.g. in public sessions). cf)
const user_manager::User* user =
if (!IsArcAllowedForUser(user)) {
VLOG_IF(1, should_report_reason) << "ARC is not allowed for the user.";
return false;
if (!user->IsAffiliated() && !IsUnaffiliatedArcAllowed()) {
VLOG_IF(1, should_report_reason)
<< "Device admin disallowed ARC for unaffiliated users.";
return false;
// Do not run ARC instance when supervised user is being created.
// Otherwise noisy notification may be displayed.
chromeos::UserFlow* user_flow =
if (!user_flow || !user_flow->CanStartArc()) {
VLOG_IF(1, should_report_reason)
<< "ARC is not allowed in the current user flow.";
return false;
return true;
} // namespace
bool IsArcAllowedForProfile(const Profile* profile) {
// Silently ignore default, lock screen and incognito profiles.
if (!profile || chromeos::ProfileHelper::IsSigninProfile(profile) ||
profile->IsOffTheRecord() ||
chromeos::ProfileHelper::IsLockScreenAppProfile(profile)) {
return false;
auto it = g_profile_status_check.Get().find(profile);
const bool first_check = it == g_profile_status_check.Get().end();
const bool result =
IsArcAllowedForProfileInternal(profile, first_check /* report_reason */);
if (first_check) {
g_profile_status_check.Get()[profile] = result;
return result;
// This is next check. We should be persistent and report the same result.
if (result != it->second) {
NOTREACHED() << "ARC allowed was changed for the current user session "
<< "and profile " << profile->GetPath().MaybeAsASCII()
<< ". This may lead to unexpected behavior. ARC allowed is"
<< " forced to " << it->second;
return it->second;
bool IsArcProvisioned(const Profile* profile) {
return profile && profile->GetPrefs()->HasPrefPath(prefs::kArcSignedIn) &&
void ResetArcAllowedCheckForTesting(const Profile* profile) {
bool IsArcMigrationAllowedByPolicyForProfile(const Profile* profile) {
// Always allow migration for unmanaged users.
if (!profile || !policy_util::IsAccountManaged(profile))
return true;
// Use the profile path as unique identifier for profile.
const base::FilePath path = profile->GetPath();
auto iter = g_is_arc_migration_allowed.Get().find(path);
if (iter == g_is_arc_migration_allowed.Get().end()) {
iter = g_is_arc_migration_allowed.Get()
.emplace(path, IsArcMigrationAllowedInternal(profile))
return iter->second;
bool IsArcBlockedDueToIncompatibleFileSystem(const Profile* profile) {
const user_manager::User* user =
// Return true for public accounts as they only have ext4 and
// for ARC kiosk as migration to ext4 should always be triggered.
// Without this check it fails to start after browser crash as
// compatibility info is stored in RAM.
if (user && (user->GetType() == user_manager::USER_TYPE_PUBLIC_ACCOUNT ||
user->GetType() == user_manager::USER_TYPE_ARC_KIOSK_APP)) {
return false;
// Test runs on Linux workstation does not have expected /etc/lsb-release
// field nor profile creation step. Hence it returns a dummy test value.
if (!base::SysInfo::IsRunningOnChromeOS())
return g_arc_blocked_due_to_incompatible_filesystem_for_testing;
// Conducts the actual check, only when running on a real Chrome OS device.
return !IsArcCompatibleFileSystemUsedForUser(user);
void SetArcBlockedDueToIncompatibleFileSystemForTesting(bool block) {
g_arc_blocked_due_to_incompatible_filesystem_for_testing = block;
bool IsArcCompatibleFileSystemUsedForUser(const user_manager::User* user) {
// Returns false for profiles not associated with users (like sign-in profile)
if (!user)
return false;
// chromeos::UserSessionManager does the actual file system check and stores
// the result to prefs, so that it survives crash-restart.
FileSystemCompatibilityState filesystem_compatibility =
const bool is_filesystem_compatible =
filesystem_compatibility != kFileSystemIncompatible ||
g_known_compatible_users.Get().count(user->GetAccountId()) != 0;
// To run ARC we want to make sure the underlying file system is compatible
// with ARC.
if (!is_filesystem_compatible) {
<< "ARC is not supported since the user hasn't migrated to dircrypto.";
return false;
return true;
void DisallowArcForTesting() {
g_disallow_for_testing = true;
bool IsArcPlayStoreEnabledForProfile(const Profile* profile) {
return IsArcAllowedForProfile(profile) &&
bool IsArcPlayStoreEnabledPreferenceManagedForProfile(const Profile* profile) {
if (!IsArcAllowedForProfile(profile)) {
LOG(DFATAL) << "ARC is not allowed for profile";
return false;
return profile->GetPrefs()->IsManagedPreference(prefs::kArcEnabled);
bool SetArcPlayStoreEnabledForProfile(Profile* profile, bool enabled) {
if (IsArcPlayStoreEnabledPreferenceManagedForProfile(profile)) {
if (enabled && !IsArcPlayStoreEnabledForProfile(profile)) {
LOG(WARNING) << "Attempt to enable disabled by policy ARC.";
return false;
VLOG(1) << "Google-Play-Store-enabled pref is managed. Request to "
<< (enabled ? "enable" : "disable") << " Play Store is not stored";
// Need update ARC session manager manually for managed case in order to
// keep its state up to date, otherwise it may stuck with enabling
// request.
// TODO(khmel): Consider finding the better way handling this.
ArcSessionManager* arc_session_manager = ArcSessionManager::Get();
// |arc_session_manager| can be nullptr in unit_tests.
if (!arc_session_manager)
return false;
if (enabled)
return true;
profile->GetPrefs()->SetBoolean(prefs::kArcEnabled, enabled);
return true;
bool AreArcAllOptInPreferencesIgnorableForProfile(const Profile* profile) {
// For Active Directory users, a LaForge account is created, where
// backup&restore and location services are not supported, hence the policies
// are unused.
if (IsActiveDirectoryUserForProfile(profile))
return true;
// Otherwise, the preferences are ignorable iff both backup&restore and
// location services are set off by policy.
const PrefService* prefs = profile->GetPrefs();
if (!prefs->IsManagedPreference(prefs::kArcBackupRestoreEnabled) ||
prefs->GetBoolean(prefs::kArcBackupRestoreEnabled) == true) {
return false;
if (!prefs->IsManagedPreference(prefs::kArcLocationServiceEnabled) ||
prefs->GetBoolean(prefs::kArcLocationServiceEnabled) == true) {
return false;
return true;
bool IsActiveDirectoryUserForProfile(const Profile* profile) {
const user_manager::User* user =
return user ? user->IsActiveDirectoryUser() : false;
bool IsArcOobeOptInActive() {
// No OOBE is expected in case Play Store is not available.
if (!IsPlayStoreAvailable())
return false;
// Check if Chrome OS OOBE flow is currently showing.
// TODO(b/65861628): Redesign the OptIn flow since there is no longer reason
// to have two different OptIn flows.
if (!chromeos::LoginDisplayHost::default_host())
return false;
// Use the legacy logic for first sign-in OOBE OptIn flow. Make sure the user
// is new.
return user_manager::UserManager::Get()->IsCurrentUserNew();
bool IsArcOobeOptInConfigurationBased() {
// Ignore if not applicable.
if (!IsArcOobeOptInActive())
return false;
// Check that configuration exist.
auto* oobe_configuration = chromeos::OobeConfiguration::Get();
if (!oobe_configuration)
return false;
if (!oobe_configuration->CheckCompleted())
return false;
// Check configuration value that triggers automatic ARC TOS acceptance.
auto& configuration = oobe_configuration->GetConfiguration();
auto* auto_accept = configuration.FindKeyOfType(
chromeos::configuration::kArcTosAutoAccept, base::Value::Type::BOOLEAN);
if (!auto_accept)
return false;
return auto_accept->GetBool();
bool IsArcTermsOfServiceNegotiationNeeded(const Profile* profile) {
// Skip to show UI asking users to set up ARC OptIn preferences, if all of
// them are managed by the admin policy. Note that the ToS agreement is anyway
// not shown in the case of the managed ARC.
if (IsArcPlayStoreEnabledPreferenceManagedForProfile(profile) &&
AreArcAllOptInPreferencesIgnorableForProfile(profile) &&
!ShouldShowOptInForTesting()) {
VLOG(1) << "All opt-in preferences are under managed. "
<< "Skip ARC Terms of Service negotiation.";
return false;
// If it is marked that the Terms of service is accepted already,
// just skip the negotiation with user, and start Android management
// check directly.
// This happens, e.g., when a user accepted the Terms of service on Opt-in
// flow, but logged out before ARC sign in procedure was done. Then, logs
// in again.
if (profile->GetPrefs()->GetBoolean(prefs::kArcTermsAccepted)) {
VLOG(1) << "The user already accepts ARC Terms of Service.";
return false;
return true;
bool IsArcTermsOfServiceOobeNegotiationNeeded() {
if (!IsPlayStoreAvailable()) {
VLOG(1) << "Skip ARC Terms of Service screen because Play Store is not "
"available on the device.";
return false;
// Demo mode setup flow runs before user is created, therefore this condition
// needs to be checked before any user related ones.
if (IsArcDemoModeSetupFlow())
return true;
if (!user_manager::UserManager::Get()->IsUserLoggedIn()) {
VLOG(1) << "Skip ARC Terms of Service screen because user is not "
<< "logged in.";
return false;
const Profile* profile = ProfileManager::GetActiveUserProfile();
if (!IsArcAllowedForProfile(profile)) {
VLOG(1) << "Skip ARC Terms of Service screen because ARC is not allowed.";
return false;
if (profile->GetPrefs()->IsManagedPreference(prefs::kArcEnabled) &&
!profile->GetPrefs()->GetBoolean(prefs::kArcEnabled)) {
VLOG(1) << "Skip ARC Terms of Service screen because ARC is disabled.";
return false;
if (IsActiveDirectoryUserForProfile(profile)) {
VLOG(1) << "Skip ARC Terms of Service screen because it does not apply to "
"Active Directory users.";
return false;
if (!IsArcTermsOfServiceNegotiationNeeded(profile)) {
VLOG(1) << "Skip ARC Terms of Service screen because it is already "
"accepted or fully controlled by policy.";
return false;
return true;
bool IsArcStatsReportingEnabled() {
// Public session users never saw the consent for stats reporting even if the
// admin forced the pref by a policy.
if (profiles::IsPublicSession()) {
return false;
bool pref = false;
return pref;
bool IsArcDemoModeSetupFlow() {
return chromeos::DemoSetupController::IsOobeDemoSetupFlowInProgress();
void UpdateArcFileSystemCompatibilityPrefIfNeeded(
const AccountId& account_id,
const base::FilePath& profile_path,
base::OnceClosure callback) {
// If ARC is not available, skip the check.
// This shortcut is just for merginally improving the log-in performance on
// old devices without ARC. We can always safely remove the following 4 lines
// without changing any functionality when, say, the code clarity becomes
// more important in the future.
if (!IsArcAvailable() && !IsArcKioskAvailable()) {
// If the compatibility has been already confirmed, skip the check.
if (GetFileSystemCompatibilityPref(account_id) != kFileSystemIncompatible) {
// Otherwise, check the underlying filesystem.
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::BindOnce(&IsArcCompatibleFilesystem, profile_path),
base::BindOnce(&StoreCompatibilityCheckResult, account_id,
ash::mojom::AssistantAllowedState IsAssistantAllowedForProfile(
const Profile* profile) {
if (!chromeos::switches::IsAssistantEnabled()) {
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_FLAG;
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile))
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_NONPRIMARY_USER;
if (profile->IsOffTheRecord())
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_INCOGNITO;
if (profile->IsLegacySupervised())
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_SUPERVISED_USER;
if (profile->IsChild())
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_CHILD_USER;
if (chromeos::DemoSession::IsDeviceInDemoMode())
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_DEMO_MODE;
if (user_manager::UserManager::Get()->IsLoggedInAsPublicAccount())
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_PUBLIC_SESSION;
const std::string kAllowedLocales[] = {ULOC_US, ULOC_UK, ULOC_CANADA,
const PrefService* prefs = profile->GetPrefs();
std::string pref_locale =
// Also accept runtime locale which maybe an approximation of user's pref
// locale.
const std::string kRuntimeLocale = icu::Locale::getDefault().getName();
if (!pref_locale.empty()) {
base::ReplaceChars(pref_locale, "-", "_", &pref_locale);
bool disallowed = !base::ContainsValue(kAllowedLocales, pref_locale) &&
!base::ContainsValue(kAllowedLocales, kRuntimeLocale);
if (disallowed && base::CommandLine::ForCurrentProcess()
.find(pref_locale) == std::string::npos) {
return ash::mojom::AssistantAllowedState::DISALLOWED_BY_LOCALE;
return ash::mojom::AssistantAllowedState::ALLOWED;
ArcSupervisionTransition GetSupervisionTransition(const Profile* profile) {
const ArcSupervisionTransition supervision_transition =
const bool is_child_to_regular_enabled =
const bool is_regular_to_child_enabled =
switch (supervision_transition) {
case ArcSupervisionTransition::NO_TRANSITION:
// Do nothing.
case ArcSupervisionTransition::CHILD_TO_REGULAR:
if (!is_child_to_regular_enabled)
return ArcSupervisionTransition::NO_TRANSITION;
case ArcSupervisionTransition::REGULAR_TO_CHILD:
if (!is_regular_to_child_enabled)
return ArcSupervisionTransition::NO_TRANSITION;
return supervision_transition;
bool IsPlayStoreAvailable() {
if (ShouldArcAlwaysStartWithNoPlayStore())
return false;
if (!IsRobotOrOfflineDemoAccountMode())
return true;
// Demo Mode is the only public session scenario that can launch Play.
return chromeos::DemoSession::IsDeviceInDemoMode() &&
} // namespace arc