chromium/chrome/browser/ash/plugin_vm/plugin_vm_features.cc

// 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/plugin_vm/plugin_vm_features.h"

#include "base/feature_list.h"
#include "base/system/sys_info.h"
#include "chrome/browser/ash/guest_os/virtual_machines/virtual_machines_util.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"

namespace plugin_vm {

namespace {

using ProfileSupported = PluginVmFeatures::ProfileSupported;
using PolicyConfigured = PluginVmFeatures::PolicyConfigured;

ProfileSupported CheckProfileSupported(const Profile* profile) {
  if (!profile) {
    VLOG(1) << "profile == nullptr";
    return ProfileSupported::kErrorNotSupported;
  }

  if (!ash::ProfileHelper::IsPrimaryProfile(profile)) {
    return ProfileSupported::kErrorNonPrimary;
  }

  if (profile->IsChild()) {
    return ProfileSupported::kErrorChildAccount;
  }

  if (profile->IsOffTheRecord()) {
    return ProfileSupported::kErrorOffTheRecord;
  }

  if (ash::ProfileHelper::IsEphemeralUserProfile(profile)) {
    return ProfileSupported::kErrorEphemeral;
  }

  if (!ash::ProfileHelper::IsUserProfile(profile)) {
    VLOG(1) << "non-regular profile is not supported";
    // If this happens, the profile is for something like the sign in screen or
    // lock screen. Return a generic error code because the user will not be
    // able to see the error code/message anyway.
    return ProfileSupported::kErrorNotSupported;
  }

  return ProfileSupported::kOk;
}

PolicyConfigured CheckPolicyConfigured(const Profile* profile) {
  // Bypass other checks when a fake policy is set, or running linux-chromeos.
  if (FakeLicenseKeyIsSet() || !base::SysInfo::IsRunningOnChromeOS()) {
    return PolicyConfigured::kOk;
  }

  if (profile == nullptr) {
    VLOG(1) << "Profile is null";
    return PolicyConfigured::kErrorUnableToCheckPolicy;
  }

  // Check that the device is enterprise enrolled.
  if (!ash::InstallAttributes::Get()->IsEnterpriseManaged()) {
    return PolicyConfigured::kErrorNotEnterpriseEnrolled;
  }

  // Check that the user is affiliated.
  const user_manager::User* const user =
      ash::ProfileHelper::Get()->GetUserByProfile(profile);
  if (user == nullptr || !user->IsAffiliated()) {
    return PolicyConfigured::kErrorUserNotAffiliated;
  }

  // Check that VirtualMachines are allowed by policy.
  if (!virtual_machines::AreVirtualMachinesAllowedByPolicy()) {
    return PolicyConfigured::kErrorVirtualMachinesNotAllowed;
  }

  // Check that PluginVm is allowed to run by policy.
  bool plugin_vm_allowed_for_device;
  if (!ash::CrosSettings::Get()->GetBoolean(ash::kPluginVmAllowed,
                                            &plugin_vm_allowed_for_device)) {
    return PolicyConfigured::kErrorUnableToCheckDevicePolicy;
  }

  if (!plugin_vm_allowed_for_device) {
    return PolicyConfigured::kErrorNotAllowedByDevicePolicy;
  }

  bool plugin_vm_allowed_for_user =
      profile->GetPrefs()->GetBoolean(plugin_vm::prefs::kPluginVmAllowed);
  if (!plugin_vm_allowed_for_user) {
    return PolicyConfigured::kErrorNotAllowedByUserPolicy;
  }

  if (GetPluginVmUserIdForProfile(profile).empty()) {
    return PolicyConfigured::kErrorLicenseNotSetUp;
  }

  return PolicyConfigured::kOk;
}

}  // namespace

bool PluginVmFeatures::IsAllowedDiagnostics::IsOk() const {
  return device_supported && profile_supported == ProfileSupported::kOk &&
         policy_configured == PolicyConfigured::kOk;
}

std::string PluginVmFeatures::IsAllowedDiagnostics::GetTopError() const {
  if (!device_supported) {
    return "Parallels Desktop is not supported on this device";
  }

  switch (profile_supported) {
    case ProfileSupported::kOk:
      break;
    case ProfileSupported::kErrorNonPrimary:
      return "Parallels Desktop is only allowed in primary user sessions";
    case ProfileSupported::kErrorChildAccount:
      return "Child accounts are not supported";
    case ProfileSupported::kErrorOffTheRecord:
      return "Guest profiles are not supported";
    case ProfileSupported::kErrorEphemeral:
      return "Ephemeral user profiles are not supported";
    case ProfileSupported::kErrorNotSupported:
      return "This user session is not allowed to run Parallels Desktop";
  }

  switch (policy_configured) {
    case PolicyConfigured::kOk:
      break;
    case PolicyConfigured::kErrorUnableToCheckPolicy:
      return "Unable to check policy";
    case PolicyConfigured::kErrorNotEnterpriseEnrolled:
      return "This is not an enterprise-managed device";
    case PolicyConfigured::kErrorUserNotAffiliated:
      return "This user is not affiliated with the organization";
    case PolicyConfigured::kErrorUnableToCheckDevicePolicy:
      return "Unable to determine if device-level policy allows running VMs";
    case PolicyConfigured::kErrorNotAllowedByDevicePolicy:
      return "VMs are disallowed by policy on this device";
    case PolicyConfigured::kErrorNotAllowedByUserPolicy:
      return "VMs are disallowed by policy";
    case PolicyConfigured::kErrorLicenseNotSetUp:
      return "License for the product is not set up in policy";
    case PolicyConfigured::kErrorVirtualMachinesNotAllowed:
      return "No Virtual Machines are allowed on this device";
  }

  return "";
}

static PluginVmFeatures* g_plugin_vm_features = nullptr;

PluginVmFeatures* PluginVmFeatures::Get() {
  if (!g_plugin_vm_features) {
    g_plugin_vm_features = new PluginVmFeatures();
  }
  return g_plugin_vm_features;
}

void PluginVmFeatures::SetForTesting(PluginVmFeatures* features) {
  g_plugin_vm_features = features;
}

PluginVmFeatures::PluginVmFeatures() = default;

PluginVmFeatures::~PluginVmFeatures() = default;

// For PluginVm to be allowed:
// * PluginVm feature should be enabled.
// * Profile should be eligible.
// * Device should be enterprise enrolled:
//   * User should be affiliated.
//   * PluginVmAllowed device policy should be set to true.
//   * UserPluginVmAllowed user policy should be set to true.
// * PluginVmUserId policy is set.
PluginVmFeatures::IsAllowedDiagnostics
PluginVmFeatures::GetIsAllowedDiagnostics(const Profile* profile) {
  auto diagnostics = IsAllowedDiagnostics{
      /*device_supported=*/base::FeatureList::IsEnabled(features::kPluginVm),
      /*profile_supported=*/CheckProfileSupported(profile),
      /*policy_configured=*/CheckPolicyConfigured(profile),
  };
  if (!diagnostics.IsOk()) {
    VLOG(1) << diagnostics.GetTopError();
  }

  return diagnostics;
}

bool PluginVmFeatures::IsAllowed(const Profile* profile, std::string* reason) {
  auto diagnostics = GetIsAllowedDiagnostics(profile);
  if (!diagnostics.IsOk()) {
    if (reason) {
      *reason = diagnostics.GetTopError();
      DCHECK(!reason->empty());
    }
    return false;
  }

  return true;
}

bool PluginVmFeatures::IsConfigured(const Profile* profile) {
  return profile->GetPrefs()->GetBoolean(
      plugin_vm::prefs::kPluginVmImageExists);
}

bool PluginVmFeatures::IsEnabled(const Profile* profile) {
  return PluginVmFeatures::Get()->IsAllowed(profile) &&
         PluginVmFeatures::Get()->IsConfigured(profile);
}

}  // namespace plugin_vm