blob: 5e182e37456272e8b38f366927cba99cdac820ea [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 <set>
#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/sys_info.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.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/user_flow.h"
#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/chromeos_switches.h"
#include "components/arc/arc_prefs.h"
#include "components/arc/arc_util.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"
namespace arc {
namespace {
constexpr char kLsbReleaseArcVersionKey[] = "CHROMEOS_ARC_ANDROID_SDK_VERSION";
constexpr char kAndroidMSdkVersion[] = "23";
// Contains set of profiles for which decline reson was already reported.
base::LazyInstance<std::set<base::FilePath>>::DestructorAtExit
g_profile_declined_set = 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.
bool g_arc_blocked_due_to_incomaptible_filesystem_for_testing = false;
// TODO(kinaba): Temporary workaround for crbug.com/729034.
//
// 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.
base::LazyInstance<std::set<AccountId>>::DestructorAtExit
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::ThreadRestrictions::AssertIOAllowed();
// 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;
user_manager::known_user::GetIntegerPref(
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,
const base::Closure& callback,
bool is_compatible) {
if (is_compatible) {
user_manager::known_user::SetIntegerPref(
account_id, prefs::kArcCompatibleFilesystemChosen,
arc::kFileSystemCompatible);
// 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) !=
arc::kFileSystemCompatible) {
g_known_compatible_users.Get().insert(account_id);
}
}
callback.Run();
}
// Returns true if this is called first time for profile. Used to detect
// duplicate message for the same profile.
bool IsReportingFirstTimeForProfile(const Profile* profile) {
const base::FilePath path = profile->GetPath();
bool inserted;
std::tie(std::ignore, inserted) = g_profile_declined_set.Get().insert(path);
return inserted;
}
} // namespace
bool IsArcAllowedForProfile(const Profile* profile) {
// Silently ignore default and lock screen profiles.
if (!profile || chromeos::ProfileHelper::IsSigninProfile(profile) ||
chromeos::ProfileHelper::IsLockScreenAppProfile(profile)) {
return false;
}
if (g_disallow_for_testing) {
VLOG_IF(1, IsReportingFirstTimeForProfile(profile))
<< "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, IsReportingFirstTimeForProfile(profile))
<< "ARC is not available.";
return false;
}
if (!chromeos::ProfileHelper::IsPrimaryProfile(profile)) {
VLOG_IF(1, IsReportingFirstTimeForProfile(profile))
<< "Non-primary users are not supported in ARC.";
return false;
}
// IsPrimaryProfile can return true for an incognito profile corresponding
// to the primary profile, but ARC does not support it.
if (profile->IsOffTheRecord()) {
VLOG_IF(1, IsReportingFirstTimeForProfile(profile))
<< "Incognito profile is not supported in ARC.";
return false;
}
if (profile->IsLegacySupervised()) {
VLOG_IF(1, IsReportingFirstTimeForProfile(profile))
<< "Supervised users are not supported in ARC.";
return false;
}
if (!IsArcCompatibleFileSystemUsedForProfile(profile) &&
!IsArcMigrationAllowedByPolicyForProfile(profile)) {
VLOG_IF(1, IsReportingFirstTimeForProfile(profile))
<< "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) crbug.com/605545
const user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
if (!IsArcAllowedForUser(user)) {
VLOG_IF(1, IsReportingFirstTimeForProfile(profile))
<< "ARC is not allowed for the user.";
return false;
}
// Do not run ARC instance when supervised user is being created.
// Otherwise noisy notification may be displayed.
chromeos::UserFlow* user_flow =
chromeos::ChromeUserManager::Get()->GetUserFlow(user->GetAccountId());
if (!user_flow || !user_flow->CanStartArc()) {
VLOG_IF(1, IsReportingFirstTimeForProfile(profile))
<< "ARC is not allowed in the current user flow.";
return false;
}
return true;
}
bool IsArcMigrationAllowedByPolicyForProfile(const Profile* profile) {
if (!profile || !profile->GetPrefs()->IsManagedPreference(
prefs::kEcryptfsMigrationStrategy)) {
return true;
}
int migration_strategy =
profile->GetPrefs()->GetInteger(prefs::kEcryptfsMigrationStrategy);
// |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: crbug.com/761348.
if (migration_strategy ==
static_cast<int>(
arc::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).
DCHECK(profile->GetPrefs()->IsManagedPreference(prefs::kArcEnabled));
// 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) &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcTransitionMigrationRequired);
}
return migration_strategy !=
static_cast<int>(
arc::policy_util::EcryptfsMigrationAction::kDisallowMigration);
}
bool IsArcBlockedDueToIncompatibleFileSystem(const Profile* profile) {
// 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_incomaptible_filesystem_for_testing;
// Conducts the actual check, only when running on a real Chrome OS device.
return !IsArcCompatibleFileSystemUsedForProfile(profile);
}
void SetArcBlockedDueToIncompatibleFileSystemForTesting(bool block) {
g_arc_blocked_due_to_incomaptible_filesystem_for_testing = block;
}
bool IsArcCompatibleFileSystemUsedForProfile(const Profile* profile) {
const user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
// 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 =
GetFileSystemCompatibilityPref(user->GetAccountId());
const bool is_filesystem_compatible =
filesystem_compatibility != kFileSystemIncompatible ||
g_known_compatible_users.Get().count(user->GetAccountId()) != 0;
std::string arc_sdk_version;
const bool is_M = base::SysInfo::GetLsbReleaseValue(kLsbReleaseArcVersionKey,
&arc_sdk_version) &&
arc_sdk_version == kAndroidMSdkVersion;
// To run ARC we want to make sure either
// - Underlying file system is compatible with ARC, or
// - SDK version is M.
if (!is_filesystem_compatible && !is_M) {
VLOG(1)
<< "Users with SDK version (" << arc_sdk_version
<< ") are not supported when they postponed to migrate to dircrypto.";
return false;
}
return true;
}
void DisallowArcForTesting() {
g_disallow_for_testing = true;
}
bool IsArcPlayStoreEnabledForProfile(const Profile* profile) {
return IsArcAllowedForProfile(profile) &&
profile->GetPrefs()->GetBoolean(prefs::kArcEnabled);
}
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) {
DCHECK(IsArcAllowedForProfile(profile));
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)
arc_session_manager->RequestEnable();
else
arc_session_manager->RequestDisable();
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;
if (profile->GetPrefs()->IsManagedPreference(
prefs::kArcBackupRestoreEnabled) &&
profile->GetPrefs()->IsManagedPreference(
prefs::kArcLocationServiceEnabled)) {
return true;
}
return false;
}
bool IsActiveDirectoryUserForProfile(const Profile* profile) {
const user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
return user ? user->IsActiveDirectoryUser() : false;
}
void UpdateArcFileSystemCompatibilityPrefIfNeeded(
const AccountId& account_id,
const base::FilePath& profile_path,
const base::Closure& callback) {
DCHECK(!callback.is_null());
// 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()) {
callback.Run();
return;
}
// If the compatibility has been already confirmed, skip the check.
if (GetFileSystemCompatibilityPref(account_id) != kFileSystemIncompatible) {
callback.Run();
return;
}
// Otherwise, check the underlying filesystem.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::Bind(&IsArcCompatibleFilesystem, profile_path),
base::Bind(&StoreCompatibilityCheckResult, account_id, callback));
}
} // namespace arc