chromium/chrome/browser/ash/crostini/crostini_features.cc

// Copyright 2019 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/crostini/crostini_features.h"

#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/guest_os/virtual_machines/virtual_machines_util.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"

namespace {

bool IsUnaffiliatedCrostiniAllowedByPolicy() {
  bool unaffiliated_crostini_allowed;
  if (ash::CrosSettings::Get()->GetBoolean(
          ash::kDeviceUnaffiliatedCrostiniAllowed,
          &unaffiliated_crostini_allowed)) {
    return unaffiliated_crostini_allowed;
  }
  // If device policy is not set, allow Crostini.
  return true;
}

bool IsArcManagedAdbSideloadingAllowedByUserPolicy(
    crostini::CrostiniArcAdbSideloadingUserAllowanceMode user_policy) {
  if (user_policy ==
      crostini::CrostiniArcAdbSideloadingUserAllowanceMode::kAllow) {
    return true;
  }

  DVLOG(1) << "adb sideloading is not allowed by the user policy";
  return false;
}

using CanChangeAdbSideloadingCallback =
    crostini::CrostiniFeatures::CanChangeAdbSideloadingCallback;

// Reads the value of DeviceCrostiniArcAdbSideloadingAllowed. If the value is
// temporarily untrusted the |callback| will be invoked later when trusted
// values are available.
void CanChangeAdbSideloadingOnManagedDevice(
    CanChangeAdbSideloadingCallback callback,
    bool is_profile_enterprise_managed,
    bool is_affiliated_user,
    crostini::CrostiniArcAdbSideloadingUserAllowanceMode user_policy) {
  // Split |callback| in 2 OnceCallbacks. This is necessary to cater to the
  // somewhat awkward PrepareTrustedValues interface, which for some return
  // values invokes the callback passed to it, and for others requires the code
  // here to do so.
  auto split_callback = base::SplitOnceCallback(std::move(callback));

  auto* const cros_settings = ash::CrosSettings::Get();
  auto status = cros_settings->PrepareTrustedValues(base::BindOnce(
      &CanChangeAdbSideloadingOnManagedDevice, std::move(split_callback.first),
      is_profile_enterprise_managed, is_affiliated_user, user_policy));

  if (status != ash::CrosSettingsProvider::TRUSTED) {
    return;
  }

  // Get the updated policy.
  int crostini_arc_abd_sideloading_device_allowance_mode = -1;
  if (!cros_settings->GetInteger(
          ash::kDeviceCrostiniArcAdbSideloadingAllowed,
          &crostini_arc_abd_sideloading_device_allowance_mode)) {
    // If the device policy is not set, adb sideloading is not allowed
    DVLOG(1) << "adb sideloading device policy is not set, therefore "
                "sideloading is not allowed";
    std::move(split_callback.second).Run(false);
    return;
  }

  using Mode =
      enterprise_management::DeviceCrostiniArcAdbSideloadingAllowedProto;

  bool is_adb_sideloading_allowed_by_device_policy;
  switch (crostini_arc_abd_sideloading_device_allowance_mode) {
    case Mode::DISALLOW:
    case Mode::DISALLOW_WITH_POWERWASH:
      is_adb_sideloading_allowed_by_device_policy = false;
      break;
    case Mode::ALLOW_FOR_AFFILIATED_USERS:
      is_adb_sideloading_allowed_by_device_policy = true;
      break;
    default:
      is_adb_sideloading_allowed_by_device_policy = false;
      break;
  }

  if (is_adb_sideloading_allowed_by_device_policy) {
    if (is_profile_enterprise_managed) {
      if (!is_affiliated_user) {
        DVLOG(1) << "adb sideloading not allowed because user is not "
                    "affiliated with the device";
        std::move(split_callback.second).Run(false);
        return;
      }
      std::move(split_callback.second)
          .Run(IsArcManagedAdbSideloadingAllowedByUserPolicy(user_policy));
      return;
    }

    DVLOG(1) << "adb sideloading is unsupported for this managed device";
    std::move(split_callback.second).Run(false);
    return;
  }

  DVLOG(1) << "adb sideloading is not allowed by the device policy";
  std::move(split_callback.second).Run(false);
}

void CanChangeManagedAdbSideloading(
    bool is_device_enterprise_managed,
    bool is_profile_enterprise_managed,
    bool is_owner_profile,
    bool is_affiliated_user,
    crostini::CrostiniArcAdbSideloadingUserAllowanceMode user_policy,
    CanChangeAdbSideloadingCallback callback) {
  DCHECK(is_device_enterprise_managed || is_profile_enterprise_managed);

  if (!base::FeatureList::IsEnabled(
          ash::features::kArcManagedAdbSideloadingSupport)) {
    DVLOG(1) << "adb sideloading is disabled by a feature flag";
    std::move(callback).Run(false);
    return;
  }

  if (is_device_enterprise_managed) {
    CanChangeAdbSideloadingOnManagedDevice(std::move(callback),
                                           is_profile_enterprise_managed,
                                           is_affiliated_user, user_policy);
    return;
  }

  if (is_owner_profile) {
    // We know here that the profile is enterprise-managed so no need to check
    std::move(callback).Run(
        IsArcManagedAdbSideloadingAllowedByUserPolicy(user_policy));
    return;
  }

  DVLOG(1) << "Only the owner can change adb sideloading status";
  std::move(callback).Run(false);
}

}  // namespace

namespace crostini {

static CrostiniFeatures* g_crostini_features = nullptr;

CrostiniFeatures* CrostiniFeatures::Get() {
  if (!g_crostini_features) {
    g_crostini_features = new CrostiniFeatures();
  }
  return g_crostini_features;
}

void CrostiniFeatures::SetForTesting(CrostiniFeatures* features) {
  g_crostini_features = features;
}

CrostiniFeatures::CrostiniFeatures() = default;

CrostiniFeatures::~CrostiniFeatures() = default;

bool CrostiniFeatures::CouldBeAllowed(Profile* profile, std::string* reason) {
  if (!base::FeatureList::IsEnabled(features::kCrostini)) {
    VLOG(1) << "Crostini is not enabled in feature list.";
    // Prior to M105, the /dev/kvm check used the same reason string.
    *reason = "Crostini is not supported on this device";
    return false;
  }

  if (!crostini::CrostiniManager::IsDevKvmPresent()) {
    // Hardware is physically incapable, no matter what the user wants.
    VLOG(1) << "Cannot run crostini because /dev/kvm is not present.";
    *reason = "Virtualization is not supported on this device";
    return false;
  }

  if (!crostini::CrostiniManager::IsVmLaunchAllowed()) {
    VLOG(1) << "Concierge does not allow VM to be launched.";
    *reason = "Virtualization is not supported on this device";
    return false;
  }

  if (!ash::ProfileHelper::IsPrimaryProfile(profile)) {
    VLOG(1) << "Crostini UI is not allowed on non-primary profiles.";
    *reason = "Crostini is only allowed in primary user sessions";
    return false;
  }

  if (!profile || profile->IsChild() || profile->IsOffTheRecord() ||
      ash::ProfileHelper::IsEphemeralUserProfile(profile) ||
      ash::ProfileHelper::IsLockScreenAppProfile(profile)) {
    VLOG(1) << "Profile is not allowed to run crostini.";
    *reason = "This user session is not allowed to run crostini";
    return false;
  }

  return true;
}

bool CrostiniFeatures::CouldBeAllowed(Profile* profile) {
  std::string reason;
  return CouldBeAllowed(profile, &reason);
}

bool CrostiniFeatures::IsAllowedNow(Profile* profile, std::string* reason) {
  if (!CouldBeAllowed(profile, reason)) {
    return false;
  }

  const user_manager::User* user =
      ash::ProfileHelper::Get()->GetUserByProfile(profile);
  if (!IsUnaffiliatedCrostiniAllowedByPolicy() && !user->IsAffiliated()) {
    VLOG(1) << "Policy blocks unaffiliated user from running Crostini.";
    *reason = "Crostini for unaffiliated users is disabled by policy";
    return false;
  }

  const PrefService::Preference* crostini_allowed_by_policy =
      profile->GetPrefs()->FindPreference(
          crostini::prefs::kUserCrostiniAllowedByPolicy);
  if (!crostini_allowed_by_policy->GetValue()->GetBool()) {
    VLOG(1) << "kUserCrostiniAllowedByPolicy preference is false.";
    *reason = "Crostini is disabled by policy";
    return false;
  }
  if (!crostini_allowed_by_policy->IsManaged() && user->IsAffiliated()) {
    VLOG(1) << "Affiliated user is not allowed to run Crostini by default.";
    *reason = "Affiliated user is not allowed to run Crostini by default.";
    return false;
  }

  if (!virtual_machines::AreVirtualMachinesAllowedByPolicy()) {
    VLOG(1)
        << "Crostini cannot run as virtual machines are not allowed by policy.";
    *reason = "Virtual Machines are disabled by policy";
    return false;
  }

  return true;
}

bool CrostiniFeatures::IsAllowedNow(Profile* profile) {
  std::string reason;
  return IsAllowedNow(profile, &reason);
}

bool CrostiniFeatures::IsEnabled(Profile* profile) {
  return g_crostini_features->IsAllowedNow(profile) &&
         profile->GetPrefs()->GetBoolean(crostini::prefs::kCrostiniEnabled);
}

bool CrostiniFeatures::IsExportImportUIAllowed(Profile* profile) {
  return g_crostini_features->IsAllowedNow(profile) &&
         profile->GetPrefs()->GetBoolean(
             crostini::prefs::kUserCrostiniExportImportUIAllowedByPolicy);
}

bool CrostiniFeatures::IsRootAccessAllowed(Profile* profile) {
  if (base::FeatureList::IsEnabled(features::kCrostiniAdvancedAccessControls)) {
    return profile->GetPrefs()->GetBoolean(
        crostini::prefs::kUserCrostiniRootAccessAllowedByPolicy);
  }
  return true;
}

bool CrostiniFeatures::IsContainerUpgradeUIAllowed(Profile* profile) {
  return g_crostini_features->IsAllowedNow(profile);
}

void CrostiniFeatures::CanChangeAdbSideloading(
    Profile* profile,
    CanChangeAdbSideloadingCallback callback) {
  // First rule out a child account as it is a special case - a child can be an
  // owner, but ADB sideloading is currently not supported for this case
  if (profile->IsChild()) {
    DVLOG(1) << "adb sideloading is currently unsupported for child accounts";
    std::move(callback).Run(false);
    return;
  }

  // Check the managed device and/or user case
  auto* connector =
      g_browser_process->platform_part()->browser_policy_connector_ash();
  bool is_device_enterprise_managed = connector->IsDeviceEnterpriseManaged();
  bool is_profile_enterprise_managed =
      profile->GetProfilePolicyConnector()->IsManaged();
  bool is_owner_profile = ash::ProfileHelper::IsOwnerProfile(profile);
  if (is_device_enterprise_managed || is_profile_enterprise_managed) {
    auto user_policy =
        static_cast<crostini::CrostiniArcAdbSideloadingUserAllowanceMode>(
            profile->GetPrefs()->GetInteger(
                crostini::prefs::kCrostiniArcAdbSideloadingUserPref));

    const auto* user = ash::ProfileHelper::Get()->GetUserByProfile(profile);
    bool is_affiliated_user = user && user->IsAffiliated();

    CanChangeManagedAdbSideloading(
        is_device_enterprise_managed, is_profile_enterprise_managed,
        is_owner_profile, is_affiliated_user, user_policy, std::move(callback));
    return;
  }

  // Here we are sure that the user is not enterprise-managed and we therefore
  // only check whether the user is the owner
  if (!is_owner_profile) {
    DVLOG(1) << "Only the owner can change adb sideloading status";
    std::move(callback).Run(false);
    return;
  }

  std::move(callback).Run(true);
}

bool CrostiniFeatures::IsPortForwardingAllowed(Profile* profile) {
  if (!profile->GetPrefs()->GetBoolean(
          crostini::prefs::kCrostiniPortForwardingAllowedByPolicy)) {
    VLOG(1) << "kCrostiniPortForwardingAllowedByPolicy preference is false.";
    return false;
  }
  // If kCrostiniPortForwardingAllowedByPolicy is not false, then we know that
  // the user is either unmanaged, the policy is not set or the policy is set
  // as true. In either of those 3 cases, port forwarding is allowed.
  return true;
}

bool CrostiniFeatures::IsMultiContainerAllowed(Profile* profile) {
  return g_crostini_features->IsAllowedNow(profile) &&
         base::FeatureList::IsEnabled(ash::features::kCrostiniMultiContainer);
}

}  // namespace crostini